// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

/* Copyright 1993 Michael B. Johnson
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any non-commercial 
 * purpose and without fee is hereby granted, provided that the 
 * above copyright notice appears in all copies.  Michael B. Johnson
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 *
 * Permission to use, copy, modify or distribute this software
 * and its documentation for any commercial purpose must be
 * confirmed in writing with Michael B. Johnson.  He can be
 * contacted at:
 *              20 Ames St. E15-023G    
 *              Cambridge, MA 02141
 *              (617) 547 0563
 *              
 */
#import "WW3DWell.h"

#import "WWTCLKit.h"

#import "WW3DCamera.h"
#import "WW3DShader.h"
#import "WW3DShape.h"
#import "WW3DShapeBrowser.h"
#import "WWEveParser.h"
#import "WWShaderArgPointMatrix.h"
#import "WWShaderArgColorWell.h"
#import "WWShaderArgTextField.h"
#import "WWDADLightView.h"

#import "EveCommand.h"
#import "WWSceneClock.h"

#import "aspectRatios.h"

#import "WWRenderWrangler.h"

#import <apps/InterfaceBuilder.h>


@implementation WW3DWell

#define BEZEL_X 7
#define BEZEL_Y 7

#define WELL_X (BEZEL_X + 2)
#define WELL_Y (BEZEL_Y + 2)


+ initialize { return [WW3DWell setVersion:5], self; }


/*
==========================================================================
subext() -- Remove extension from fname only if it is there.

==========================================================================
*/
static char 
*subext(fname, ext)
register char *fname, *ext;
{
  int fl, el;

	for ( fl = strlen(fname), el = strlen(ext);
		--el > -1 && --fl > -1 && fname[fl] == ext[el]; )
		;
	if (el == -1) 
	{  fname[fl] = '\0';
	}
	return(fname);
}


/*
==========================================================================
basename() -- deletes any prefix ending in `/' and the suffix.

==========================================================================
*/
static char 
*basename(str, sfx, dest)
char *str, *sfx, *dest;
{
char temp[1024];
char *p, *p1;
char *subext();

	p = p1 = temp;
	strcpy(p, str);
	while (*p) 
	{  if (*p++ == '/') 
           {  p1 = p;
	   }
	}
	strcpy (dest, subext(p1, sfx));
	return(dest);
}


- initFrame:(const NXRect *)r 
{
   int     width, height;
   //NXRect  newR;
   const char  *WDRAG_PBTYPE[] = { NXFilenamePboardType, NULL};


   selectedColor = NXConvertRGBToColor(0.9, 0.9, 0.0);
   unselectedColor = NXConvertRGBToColor(0.9, 0.9, 0.9);

   replyTextColor = NXConvertRGBToColor(0.9, 0.0, 0.0);
   sendTextColor = NXConvertRGBToColor(0.0, 0.0, 0.9);

   edgeClicked = NO;

   aspectRatioType = WW_ASPECT_DONT_CARE;
   width = r->size.width;
   height = r->size.height;
   aspectRatio = width/height;

   //newR.origin.x = r->origin.x;
   //newR.origin.y = r->origin.y;
   //newR.size.width = newR.size.height = width;
   //[super initFrame:&newR];

   [super initFrame:r];
   [self registerForDraggedTypes:WDRAG_PBTYPE count:1];

   if ([NXApp respondsTo:@selector(isTestingInterface)]) // we're in IB
   {  [window setDepthLimit:NX_TwelveBitRGBDepth];
   }

   bezelRect.origin.x = BEZEL_X;
   bezelRect.origin.y = BEZEL_Y;
   bezelRect.size.width = width - (2 * BEZEL_X);
   bezelRect.size.height = height - (2 * BEZEL_Y);

   wellRect.origin.x = WELL_X;
   wellRect.origin.y = WELL_Y;
   wellRect.size.width = width - (2 * WELL_X);
   wellRect.size.height = height - (2 * WELL_Y);

   // A WW3DWell has a notion of a current scene, which is going on inside of it.
   // The scene can be being built, or being replayed, or even being edited.
   // when the scene is being built or edited, the WW3DWell's sceneClock provides
   // information of where in the scene we are.  When the scene is being played
   // back, the WW3DWell's camera is responsible for providing information about
   // where in the scene we are.  One important difference is that when we are
   // looking at the scene and the sceneClock is providing the time, it is an
   // exact value.  When the scene is being played back, we can be at a region
   // of time, since the camera thinks of time as a series of frames, and a
   // frame can last for some amount of time (hence things can get motion blurred).

   // In the default, we assume that things are being simulated at a rate of 60Hz,
   // and then the default camera runs at about 15 fps, and that a given scene
   // starts at 0 and is only 10 seconds long.  For the average motion test, this
   // is fine, and if you need more, well, change it...
   sceneClock = [[WWSceneClock alloc] init];

   camera = [[WW3DCamera alloc] initFrame:&wellRect];
   [camera setAutosizing:(NX_WIDTHSIZABLE | NX_HEIGHTSIZABLE)];
   [camera setDelegate:self];
   [camera setSceneClock:sceneClock];

   // this tickles a bug in the 3.2 release for HP; don't do it
   //imageView = camera;
   imageView = nil;
   reuseImageView = YES;
   useRendribInstead = NO;
   beepWhenDone = YES;

   projectionType = N3D_Perspective;
   [camera setProjection:projectionType];

   // stuff for RIBParser
   ribParser = [[WWEveParser alloc] init]; 
   [ribParser setSceneClock:sceneClock];
   [camera setRunTimeSystem:ribParser];

   // oh boy, is this a hack...
   [ribParser setViewToUpdate:camera];
   [ribParser setWell:self];
 
   // just so everything's in synch to start...
   [ribParser setRootShape:[camera worldShape]];

   [self setAutoresizeSubviews:YES];
   if (![self addSubview:camera])
   {  return nil;
   }

   wellControlPanel = nil;
   statusBufSize = 2048;
   statusBuf = (char *)NXZoneCalloc([self zone], statusBufSize, sizeof(char));

   // need to make sure ribParser is initialized before I do this!!!
   [sceneClock setScene:ribParser];
   [sceneClock setDelegate:self];
   [sceneClock setSamplesPerSecond:60];  // do this after setting the scene!

   imageViewWindowList = [[List alloc] init];

   return self;
}

- awake
{
  const char  *WDRAG_PBTYPE[] = { NXFilenamePboardType, NULL};


  [super awake];

  [self registerForDraggedTypes:WDRAG_PBTYPE count:1];
  [camera sizeTo:wellRect.size.width :wellRect.size.height];
  [camera setDelegate:self];

  projectionType = N3D_Perspective;
  [camera setProjection:projectionType];
  replyTextColor = NXConvertRGBToColor(0.9, 0.0, 0.0);
  sendTextColor = NXConvertRGBToColor(0.0, 0.0, 0.9);
  edgeClicked = NO;

  // stuff for RIBParser
  ribParser = [[WWEveParser alloc] init]; 
  [ribParser setSceneClock:sceneClock];
  [camera setRunTimeSystem:ribParser];

  // oh boy, is this a hack...
  [ribParser setViewToUpdate:camera];
  [ribParser setWell:self];

  // just so everything's in synch to start...
  [ribParser setRootShape:[camera worldShape]];

  // these should be archived, really...
  selectedColor = NXConvertRGBToColor(0.9, 0.9, 0.0);
  unselectedColor = NXConvertRGBToColor(0.9, 0.9, 0.9);

  wellControlPanel = nil;

  statusBufSize = 2048;
  statusBuf = (char *)NXZoneCalloc([self zone], statusBufSize, sizeof(char));

   // need to make sure ribParser is initialized before I do this!!!
   [sceneClock setScene:ribParser];
   [sceneClock setDelegate:self];
   [sceneClock setSamplesPerSecond:60];  // do this after setting the scene!

   imageViewWindowList = [[List alloc] init];

   // nil the (potentially) archived
   if (imageView == camera)
   { NXLogError("warning: setting imageView to nil to avoid tickling NeXT's 3.2 bug...\n"); 
     imageView = nil;
   }

   // in case we have an old camera; we need to reset our sceneClock as the sceneClock
   [camera setSceneClock:sceneClock];

   return self;
}

- free
{
  char  tmpFilename[64];
  int   i, howMany = [imageViewWindowList count];
  id    aWindow;


  [statusText setStringValue:"freeing WW3DWell..."]; [statusText display]; NXPing();
  [self unregisterDraggedTypes];

  for (i = 0; i < howMany; i++)
  {  aWindow = [imageViewWindowList objectAt:i];
     [aWindow setFreeWhenClosed:NO];
     [aWindow close];
     [aWindow free];  // that should automatically free the movie view
  }
  [imageViewWindowList free];
  
  // we want to free parser, because we want the ribParser, which
  // might have traces set up in the rootShape of the camera to be able to
  // untrace themselves cleanly...
  [ribParser free];
  // need to make sure the camera doesn't try to free its worldShape, since
  // the ribParser just free'ed it
  [camera setWorldShape:nil];
  [camera setNoCurrentShape:nil];
  [camera removeFromSuperview];
  [camera free]; 

  // in order for the following to do the right thing, the
  // wellControlPanel has to *not* free itself when closed.
  // to make sure of this, we'll send it a don't free on 
  // close message right before we close it...
  [statusText setStringValue:"bye!"]; [statusText display]; NXPing();
  if (statusBuf) { NXZoneFree([self zone], statusBuf); }

  [sceneClockControlPanel setFreeWhenClosed:NO];
  [sceneClockControlPanel close];
  [sceneClockControlPanel free];

  [wellControlPanel setFreeWhenClosed:NO];
  [wellControlPanel close];

  // note that by free'ing the wellControlPanel, it should automatically
  // free any panels it might have open, like the shape panel...
  [wellControlPanel free];

  // blow away any tmp files I made...
  sprintf(tmpFilename, "/tmp/foo%d.rib", getpid());
  unlink(tmpFilename);
  sprintf(tmpFilename, "/tmp/foo%d.tiff", getpid());
  unlink(tmpFilename);
  
  return [super free];
}

- reinitializeStuffFromZone:(NXZone *)zone
{
  return self;
}

- copyFromZone:(NXZone *)zone
{
  id newCopy = [super copyFromZone:zone];

  [newCopy reinitializeStuffFromZone:zone];

  return newCopy;
}

#define EPSILON 10

- sizeTo:(NXCoord)width :(NXCoord)height
{
  sprintf(statusBuf, "resizing to (%f, %f)...", width, height);
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
  // don't want to make it too small...
  if (width < ((2 * BEZEL_X) + EPSILON)) return nil;
  if (height < ((2 * BEZEL_Y) + EPSILON)) return nil;


  // let's say it comes in at 100, 100 and you're supposed to be constrained to 4:3
  // you don't want it to get bigger than 100, 100, so... you frob the width, not the height
  switch (aspectRatioType)
  {  case WW_ASPECT_DONT_CARE:
            aspectRatio = width/height;
            [self revertControlPanel];
            break;
     case WW_ASPECT_NTSC:  // 1.33:1
            height = width/1.33;
            aspectRatio = 1.33;
            break;
     case WW_ASPECT_AMERICAN_WIDESCREEN:  // 1.85:1
            //width = height * 1.85;
            height = width/1.85;
            aspectRatio = 1.85;
            break;
     case WW_ASPECT_EUROPEAN_WIDESCREEN:  // 1.66:1
            //width = height * 1.66;
            height = width/1.66;
            aspectRatio = 1.66;
            break;
     case WW_ASPECT_VISTA_VISION:  // 2.21:1
            //width = height * 1.21;
            height = width/2.21;
            aspectRatio = 2.21;
            break;
     case WW_ASPECT_SQUARE:  // 1:1
            if (width < height)
	    {  height = width;
	    }
	    else
	    {  width = height;
	    }
            aspectRatio = 1.0;
            break;
     case WW_ASPECT_CUSTOM:  //aspectX:aspectY
            if (aspectRatio < 1.0)
	    {  width = height * aspectRatio;
	    }
	    else
	    {  height = width/aspectRatio;
	    }
            break;
     default:
            NXLogError("unknown aspect ratio type: %d", aspectRatioType);
            break;
  }

  bezelRect.size.width = width - (2 * BEZEL_X);
  bezelRect.size.height = height - (2 * BEZEL_Y);

  wellRect.size.width = width - (2 * WELL_X);
  wellRect.size.height = height - (2 * WELL_Y);

  [super sizeTo:width :height];

  [camera sizeTo:wellRect.size.width :wellRect.size.height];
  [window display];
  sprintf(statusBuf, "resized to (%f, %f)", (width - 18), (height - 18));
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();

  return self;
}

// window delegate method to resize to right aspect ratio this should
// be used when the rib well is the only view inside a window - Make the
// rib well the delegate of the window and "you're all set..."
- windowWillResize:sender toSize:(NXSize *)frameSize
{
  switch (aspectRatioType)
  {  case WW_ASPECT_DONT_CARE:
            break;
     case WW_ASPECT_NTSC:  // 1.33:1
            frameSize->height = frameSize->width/1.33;
            break;
     case WW_ASPECT_AMERICAN_WIDESCREEN:  // 1.85:1
            frameSize->height = frameSize->width/1.85;
            break;
     case WW_ASPECT_EUROPEAN_WIDESCREEN:  // 1.66:1
            frameSize->height = frameSize->width/1.66;
            break;
     case WW_ASPECT_VISTA_VISION:  // 2.21:1
            frameSize->height = frameSize->width/2.21;
            break;
     case WW_ASPECT_SQUARE:  // 1:1
            if (frameSize->width < frameSize->height)
	    {  frameSize->height = frameSize->width;
	    }
	    else
	    {  frameSize->width = frameSize->height;
	    }
            break;
     case WW_ASPECT_CUSTOM:  //aspectX:aspectY
            if (aspectRatio < 1.0)
	    {  frameSize->width = frameSize->height * aspectRatio;
	    }
	    else
	    {  frameSize->height = frameSize->width/aspectRatio;
	    }
            break;
     default:
            NXLogError("unknown aspect ratio type: %d - unable to change window size...", aspectRatioType);
            break;
  }
  sprintf(statusBuf, "constrained window resizing to (%f, %f)...", frameSize->width, frameSize->height);
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();

  return self;
}


- empty:sender
{
  // tell the camera it's worldShape is nil
  [camera setWorldShape:nil];

  // tell the parser it's rootShape is nil
  // it's smart enough to free the old one
  [ribParser setRootShape:nil];
  return self;
}

- revertShapeInspectorFor:newShape
{
   RtMatrix  anRtMatrix;
   RtBound   boundingBox;


   // shape stuff
   [self revertSurfaceShaderInspectorFor:[newShape shaderType:SLO_TYPE_SURFACE]];
   [self revertDisplacementShaderInspectorFor:[newShape shaderType:SLO_TYPE_DISPLACEMENT]];

   [(WW3DShape *)newShape getBoundingBox:&boundingBox];
   [[boundingBoxMatrix cellAt:0 :0] setFloatValue:boundingBox[0]];
   [[boundingBoxMatrix cellAt:1 :0] setFloatValue:boundingBox[1]];
   [[boundingBoxMatrix cellAt:0 :1] setFloatValue:boundingBox[2]];
   [[boundingBoxMatrix cellAt:1 :1] setFloatValue:boundingBox[3]];
   [[boundingBoxMatrix cellAt:0 :2] setFloatValue:boundingBox[4]];
   [[boundingBoxMatrix cellAt:1 :2] setFloatValue:boundingBox[5]];
   [newShape getTransformMatrix:anRtMatrix];
   [self drawRtMatrix:anRtMatrix inMatrix:transformMatrix];

   [self fillRIBCommandsMatrix:ribCommandsMatrix];

   [boundingBoxMatrix display];
   [transformMatrix display];
   [ribCommandsMatrix display];
  
   return self;
}

- enableCameraInspector
{
   if (cameraInspectorInvalid)
   {  [eyePointMatrix setEnabled:YES];
      [viewPointMatrix setEnabled:YES];
      [rollAngleText setEnabled:YES];
      [cameraTransformMatrix setEnabled:YES];

      [focalLengthText  setEnabled:YES];
      [focalDistanceText  setEnabled:YES];
      [fStopText  setEnabled:YES];
      [exposureLengthText  setEnabled:YES];
  
      cameraInspectorInvalid = NO;
   }
  
   return self;
}

- invalidateCameraInspector
{
   if (!cameraInspectorInvalid)
   {  [eyePointMatrix setEnabled:NO];
      [viewPointMatrix setEnabled:NO];
      [rollAngleText setEnabled:NO];
      [cameraTransformMatrix setEnabled:NO];

      [focalLengthText  setEnabled:NO];
      [focalDistanceText  setEnabled:NO];
      [fStopText  setEnabled:NO];
      [exposureLengthText  setEnabled:NO];
  
      // force redisplay
      [eyePointMatrix display];
      [viewPointMatrix display];
      [cameraTransformMatrix display];

      cameraInspectorInvalid = YES;
   }
  
   return self;
}

- revertCameraInspector
{
   RtMatrix  anRtMatrix;
   RtPoint   anEyePoint, aViewPoint;
   RtFloat   aRollAngle;
   float     elf;

   [self enableCameraInspector];

   [camera getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
   [[eyePointMatrix cellAt:0 :0] setFloatValue:anEyePoint[0]];
   [[eyePointMatrix cellAt:0 :1] setFloatValue:anEyePoint[1]];
   [[eyePointMatrix cellAt:0 :2] setFloatValue:anEyePoint[2]];
   [[viewPointMatrix cellAt:0 :0] setFloatValue:aViewPoint[0]];
   [[viewPointMatrix cellAt:0 :1] setFloatValue:aViewPoint[1]];
   [[viewPointMatrix cellAt:0 :2] setFloatValue:aViewPoint[2]];
   [rollAngleText setFloatValue:aRollAngle];


   [camera getTransformMatrix:anRtMatrix];
   [self drawRtMatrix:anRtMatrix inMatrix:cameraTransformMatrix];

   [focalLengthText setFloatValue:[camera focalLength]];
   [focalDistanceText setFloatValue:[camera focalDistance]];
   [fStopText setFloatValue:[camera fStop]];
   [exposureLengthText setFloatValue:[camera exposureLength]];
  
   // WAVE: need to deal with shotOutputTypeMatrix here...

   [totalFrameCountText setIntValue:([camera endFrame] - [camera startFrame])];
   [framesPerSecondText setFloatValue:[camera framesPerSecond]];
   [shotLengthText setFloatValue:[camera shotLength]];
   [frameNumberText setFloatValue:[camera frameNumber]];

   [shotStartTimeText setFloatValue:[camera shotStartTime]];
   elf = [camera exposureLengthFactor];
   [exposureLengthSlider setFloatValue:elf];
   [exposureLengthPercentageText setFloatValue:(100. * elf)];
   if (elf == 0.0)
   {  [exposureLengthStrobeSwitch setIntValue:1];
      [exposureLengthFilmSwitch setIntValue:0];
      [exposureLengthVideoSwitch setIntValue:0];
   }
   else
   {  if (elf == 0.5)
      {  [exposureLengthStrobeSwitch setIntValue:0];
         [exposureLengthFilmSwitch setIntValue:1];
         [exposureLengthVideoSwitch setIntValue:0];
      }
      else
      {  if (elf == 1.0)
         {  [exposureLengthStrobeSwitch setIntValue:0];
            [exposureLengthFilmSwitch setIntValue:0];
            [exposureLengthVideoSwitch setIntValue:1];
         }
         else
         {  [exposureLengthStrobeSwitch setIntValue:0];
            [exposureLengthFilmSwitch setIntValue:0];
            [exposureLengthVideoSwitch setIntValue:0];
         }
      }
   }

   [binaryRIBSwitch setState:[camera binaryRIB]];
   [reuseImageViewSwitch setState:reuseImageView];
   [useRendribInsteadSwitch setState:[self useRendribInstead]];
   [beepWhenDoneSwitch setState:beepWhenDone];

   // force redisplay
   [eyePointMatrix display];
   [viewPointMatrix display];
   [cameraTransformMatrix display];
  
   cameraInspectorInvalid = YES;

   return self;
}

- imageView { return imageView; }

- setCurrentShape:newShape
{
   [camera setCurrentShape:newShape];
   [delegate wellWasUpdated:camera];
   [[shapeBrowser loadColumnZero] selectRootShape];
   return self;
}

- setWorldShape:aShape
{
   [camera setWorldShape:aShape];
   [camera setNoCurrentShape:nil];
   [camera setCurrentShape:[camera worldShape]];  // set current shape to be top
   [delegate wellWasUpdated:camera];
   [[shapeBrowser loadColumnZero] selectRootShape];
  return self;
}


- buildNewShapeHierarchyFromRIBFile:(const char *)filename 
{
   id aShape;


   sprintf(statusBuf, "building new shape hierarchy from rib file %s...", filename);
   [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  // reset the sceneClock to the start of a new scene; really should probably make sure I can unwind this...
      [sceneClock reset:nil];     
      aShape = [ribParser buildNewShapeHierarchyFromRIBFile:filename];
      if (!aShape) 
      {  NXLogError("unable to build shape hierarchy from RIB file <%s>\n", filename);
         return nil; 
      }
      [self setWorldShape:aShape];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [camera display]; 
   return self;
}

- addToShapeHierarchyFromRIBFile:(const char *)filename 
{
   id aShape;


  sprintf(statusBuf, "adding new shape hierarchy from rib file %s...", filename);
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  [ribParser setRootShapeInUse:YES];
      aShape = [ribParser buildNewShapeHierarchyFromRIBFile:filename :NO];
      if (!aShape) 
      {  NXLogError("unable to build shape hierarchy from RIB file <%s>\n", filename);
         [ribParser setRootShapeInUse:NO];
         return nil; 
      }
      [[camera currentShape] addChild:aShape]; // don't free the old one cause it might be shared - need a way to track this... 
      [self setCurrentShape:aShape];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [camera display]; 
   return self;
}

- buildNewShapeHierarchyFromEveFile:(const char *)filename 
{
   id aShape;


   sprintf(statusBuf, "building new shape hierarchy from eve file %s...", filename);
   [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  // reset the sceneClock to the start of a new scene; really should probably make sure I can unwind this...
      [sceneClock reset:nil];     
      [camera setWorldShape :nil];     
      [camera setNoCurrentShape:nil];
      aShape = [ribParser buildNewShapeHierarchyFromEveFile:filename];
      if (!aShape) 
      {  NXLogError("unable to build shape hierarchy from Eve file <%s>\n", filename);
         return nil; 
      }
      [self setWorldShape:aShape];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [camera display]; 
   return self;
}

- readMdlTemplateFile:(const char *)filename 
{
   static char  thePath[MAXPATHLEN+1];
 

   sprintf(statusBuf, "reading model template file %s...", filename);
   [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  // set the modelPath tcl variable to be the path of the .mdl file, 
      // and then tell the tcl interp to parse the file
      [[ribParser tclInterp] setVar:"modelPath" toValue:(char *)filename];
      sprintf(thePath, "%s/model.eve", filename);
      [self evaluateEveFile:thePath];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
  return self;
}

- buildNewShapeHierarchyFromMdlFile:(const char *)filename 
{
   char  thePath[MAXPATHLEN+1];
 

   sprintf(statusBuf, "building new shape hierarchy from model file package %s...", filename);
   [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  // reset the sceneClock to the start of a new scene; really should probably make sure I can unwind this...
      [sceneClock reset:nil];     
      // set the modelPath tcl variable to be the path of the .mdl file, 
      // and then tell the tcl interp to parse the file
      [[ribParser tclInterp] setVar:"modelPath" toValue:(char *)filename];
      sprintf(thePath, "%s/model.eve", filename);
      [self buildNewShapeHierarchyFromEveFile:thePath];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   return self;
}

- addToShapeHierarchyFromMdlFile:(const char *)filename 
{
   char  thePath[MAXPATHLEN+1];
 

   sprintf(statusBuf, "adding new shape hierarchy from model file package %s...", filename);
   [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  // set the modelPath tcl variable to be the path of the .mdl file, 
      // and then tell the tcl interp to parse the file
      [[ribParser tclInterp] setVar:"modelPath" toValue:(char *)filename];
      sprintf(thePath, "%s/model.eve", filename);
      [self addToShapeHierarchyFromEveFile:thePath];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   return self;
}


- buildNewShapeHierarchyFromSceneFile:(const char *)filename 
{
   id aShape;


   sprintf(statusBuf, "building new shape hierarchy from scene file %s...", filename);
   [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   {  // reset the sceneClock to the start of a new scene; really should probably make sure I can unwind this...
      [sceneClock reset:nil];     
      [camera setWorldShape :nil];     
      [camera setNoCurrentShape:nil];
      [ribParser setFrozen:YES];
      aShape = [ribParser buildNewShapeHierarchyFromEveFile:filename];
      [ribParser setFrozen:NO];
      if (!aShape) 
      {  NXLogError("unable to build shape hierarchy from Eve file <%s>\n", filename);
         return nil; 
      }
      [self setWorldShape:aShape];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [camera display]; 
   return self;
}

- addToShapeHierarchyFromSceneFile:(const char *)filename 
{
   id aShape;


  sprintf(statusBuf, "adding new shape hierarchy from eve file %s...", filename);
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   { // tell the rib parser not to blow away it's old root shape
     [ribParser setFrozen:YES];
     [ribParser setRootShapeInUse:YES];
     aShape = [ribParser buildNewShapeHierarchyFromEveFile:filename :NO];
     [ribParser setFrozen:NO];
     if (!aShape) 
     {  NXLogError("unable to build shape hierarchy from EVE file <%s>\n", filename);
        [ribParser setRootShapeInUse:NO];
        return nil; 
     }
     [[camera currentShape] addChild:aShape]; // don't free the old one cause it might be shared - need a way to track this... 
     [self setCurrentShape:aShape];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [camera display]; 
   return self;
}


- addToShapeHierarchyFromEveFile:(const char *)filename 
{
   id aShape;


  sprintf(statusBuf, "adding new shape hierarchy from eve file %s...", filename);
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
   if (filename && *filename)
   { // tell the rib parser not to blow away it's old root shape
     [ribParser setRootShapeInUse:YES];
     aShape = [ribParser buildNewShapeHierarchyFromEveFile:filename :NO];
     if (!aShape) 
     {  NXLogError("unable to build shape hierarchy from EVE file <%s>\n", filename);
        [ribParser setRootShapeInUse:NO];
        return nil; 
     }
     [[camera currentShape] addChild:aShape]; // don't free the old one cause it might be shared - need a way to track this... 
     [self setCurrentShape:aShape];
   }
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [statusText setStringValue:"calculating bounding boxes..."]; [statusText display]; NXPing();
   [[camera worldShape] calculateBoundingBoxStartingAt:[sceneClock timestamp] endingAt:[sceneClock timestamp]];
   [statusText setStringValue:"done"]; [statusText display]; NXPing();
   [camera display]; 
   return self;
}

- evaluateEveFile:(const char *)filename 
{
   if (filename && *filename)
   {  if ([ribParser evaluateEveFile:filename])
      {  return self;
      }
   }
   return nil;
}


- turnOffCropWindow { [camera turnOffCropWindow]; return self; }


- shape { return [camera worldShape]; }
- parser { return ribParser; }
- camera { return camera; }

- drawSelf:(const NXRect *)r :(int)n 
{
  NXDrawButton(r, r);
  if (edgeClicked)
  {  PSsetgray(NX_WHITE);
     PSrectfill((r->origin.x + 1), (r->origin.y + 1), (r->size.width - 2), (r->size.height - 2));
  }
  NXDrawGrayBezel(&bezelRect, &bezelRect);

  return self;
}

- windowWillClose:sender
{
  int   i = 0, 
        howMany = [imageViewWindowList count];
  BOOL  done = NO;


  if (wellControlPanel && (sender == wellControlPanel))
  { edgeClicked = NO;
    wellControlPanel = nil;
    [self display];
    return self;
  }


  // if it's one of the imageViewWindows, delete it from the list...
  while (!done && (i < howMany)) 
  {  if (sender == [imageViewWindowList objectAt:i])
     {  [imageViewWindowList removeObjectAt:i];
        done = YES;
     }
     i++;
  }
  // if it's the current imageViewWindow, nil out it and it's view
  if (sender == imageViewWindow)
  {  imageView = nil;
     imageViewWindow = nil;
  }
  return self;
}

- revertSceneClockInspector
{
  [sceneClockSamplesPerSecondText setFloatValue:[sceneClock samplesPerSecond]];
  [sceneClockTimeText setFloatValue:[sceneClock timestamp]];
  [sceneClockIncrementText setFloatValue:[sceneClock increment]];
  [sceneClockSkipText setFloatValue:[sceneClock skip]];
  [sceneClockRatioText setFloatValue:[sceneClock ratio]];

  if ([sceneClock isPaused])
  {  [sceneClockButtonMatrix selectCellWithTag:2]; // really should #define this somewhere, doofus
  }

  [sceneClockMarkText setFloatValue:[sceneClock mark]];

  // is this the best place to do this?
  [self synchCameraToSceneClock];

  return self;
}

- revertControlPanel
{
  if (!wellControlPanel)  // this should speed stuff up a lot when the panel's not around...
  {  return self;
  }
  
  [statusText setStringValue:"reverting control panel..."]; [statusText display]; NXPing();

  // note that I'm leaking here...
  [shapeBrowser setPath:[[camera currentShape] getPath]];

  [ignoreShadingRateSwitch setState:[ribParser ignoreShadingRate]];
  [ignoreColorsSwitch setState:[ribParser ignoreColors]];
  [ignoreLightsSwitch setState:[ribParser ignoreLights]];
  [ignoreShadersSwitch setState:[ribParser ignoreShaders]];
  [firmTransformsSwitch setState:[ribParser firmTransforms]];
  [treatAttributeBeginLikeStartShapeSwitch setState:[ribParser treatAttributeBeginLikeStartShape]];
  [treatTransformBeginLikeAttributeBeginSwitch setState:[ribParser treatTransformBeginLikeAttributeBegin]];
  [applyShadersDirectlyToCurrentShapeSwitch setState:[ribParser applyShadersDirectlyToCurrentShape]];
  [lowRezTesselationText setFloatValue:[camera lowRezTesselation]];
  [tesselationText setFloatValue:[camera tesselation]];
  [projectionTypeMatrix selectCellWithTag:projectionType];
  [selectedColorWell setColor:selectedColor];
  [unselectedColorWell setColor:unselectedColor];
  [shadingRateText setFloatValue:[camera shadingRate]];
  [shadingRateSlider setFloatValue:[camera shadingRate]];
  [binaryRIBSwitch setState:[camera binaryRIB]];
  if ([self useRendribInstead])
  {  [shadingRateText setEnabled:NO];
     [shadingRateSlider setEnabled:NO];
  }
  else
  {  [shadingRateText setEnabled:YES];
     [shadingRateSlider setEnabled:YES];
  }

  [aspectRatioMatrix selectCellWithTag:aspectRatioType];
  [customAspectRatioText setFloatValue:aspectRatio];
  [customAspectRatioText setEnabled:(aspectRatioType == WW_ASPECT_CUSTOM)];
  [fovText setFloatValue:[camera fieldOfView]];
  [fovSlider setFloatValue:[camera fieldOfView]];
  [trackballAffectsMatrix selectCellWithTag:[camera trackballAffects]];
  [trackballXYZMatrix selectCellWithTag:[camera trackballXYZ]];

   [renderWorldAsBoxSwitch setState:[camera renderWorldAsBox]];
  [renderCurrentAsBoxSwitch setState:[camera renderCurrentAsBox]];
  [worldIsVisibleSwitch setState:[camera worldIsVisible]];
  [currentIsVisibleSwitch setState:[camera currentIsVisible]];

  [backgroundColorWell setColor:[camera backgroundColor]];

  [showSelectedShapeSwitch setState:[camera showSelectedShape]];
  [drawOriginForSelectedShapeSwitch setState:[camera drawOriginForSelectedShape]];
  [renderStyleMatrix selectCellWithTag:[camera renderStyle]];
  [movingRenderStyleMatrix selectCellWithTag:[camera movingRenderStyle]];

  // shape stuff
  [self revertShapeInspectorFor:[camera currentShape]];

  // camera stuff
  [self revertCameraInspector];

  // scene stuff
  [self revertSceneClockInspector];
  [statusText setStringValue:"done"]; [statusText display]; NXPing();
  return self;
}

- takeShowSelectedShape:sender 
{  [camera setShowSelectedShape:[sender intValue]]; 
   [self revertControlPanel]; 
   [delegate sceneWasUpdated:camera]; 
   [self display];
   return self; 
}

- takeDrawOriginForSelectedShape:sender 
{  [camera setDrawOriginForSelectedShape:[sender intValue]]; 
   [self revertControlPanel]; 
   [delegate sceneWasUpdated:camera]; 
   [self display];
   return self; 
}

- takeRenderStyleFromMatrix:sender 
{  [camera setRenderStyle:[[sender selectedCell] tag]]; 
   [self revertControlPanel]; 
   return self; 
}

- takeMovingRenderStyleFromMatrix:sender 
{  [camera setMovingRenderStyle:[[sender selectedCell] tag]]; 
   [self revertControlPanel]; 
   return self; 
}

- loadImageView
{  id     bundle = [NXBundle bundleForClass: [self class]];
   char   path[MAXPATHLEN + 1];

   if (!bundle) 
   {  NXLogError("No bundle for Class %s - unable to loadImageView\n", [self name]);
      return nil;
   }
   if (![bundle getPath:path forResource:"WW3DWellImageView" ofType:"nib"])
   {  NXLogError("No path for WW3DWellImageView.nib.\n");
      return nil;
   }
   [NXApp loadNibFile:path owner:self  withNames:NO fromZone:[self zone]];
   //NXLogError ("Loaded %s.\n", path);
   [imageViewWindowList addObject:imageViewWindow];
   [imageView setImageIsShared:NO];

   return self;
}

- (BOOL)binaryRIB { return [camera binaryRIB]; }
- setBinaryRIB:(BOOL)n { [camera setBinaryRIB:n]; return [self revertControlPanel]; }
- takeBinaryRIB:sender { [camera takeBinaryRIB:sender]; return [self revertControlPanel]; }

- (BOOL)reuseImageView { return reuseImageView; }
- setReuseImageView:(BOOL)n { reuseImageView = n; return self; }
- takeReuseImageView:sender { reuseImageView = [sender intValue]; return self; }

- (BOOL)useRendribInstead { return [camera useRendribInstead]; }
- setUseRendribInstead:(BOOL)n { [camera setUseRendribInstead:n]; return [self revertControlPanel]; }
- takeUseRendribInstead:sender { [camera takeUseRendribInstead:sender]; return [self revertControlPanel]; }

- (BOOL)beepWhenDone { return beepWhenDone; }
- setBeepWhenDone:(BOOL)n { beepWhenDone = n; return self; }
- takeBeepWhenDone:sender { beepWhenDone = [sender intValue]; return self; }

- showImageView:sender
{
   if (imageView && reuseImageView)
   {  return self;
   }
   return [self loadImageView];
}
 

- showControlPanel:sender
{
   NXRect  scrollRect, contentRect, matrixRect;



  [[camera worldShape] setVisible:NO];
  // display myself so the fact that the edge is clicked is apparent...
  [self display];  NXPing();

  if (!wellControlPanel)
  {  id     bundle = [NXBundle bundleForClass: [self class]];
     char   path[MAXPATHLEN + 1];

     if (!bundle) 
     {  NXLogError("No bundle for Class %s - unable to showControlPanel\n", [self name]);
        return nil;
     }
     if (![bundle getPath:path forResource:"WW3DWellControlPanel" ofType:"nib"])
     {  NXLogError("No path for WW3DWellControlPanel.nib.\n");
        return nil;
     }
     [NXApp loadNibFile:path owner:self  withNames:NO fromZone:[self zone]];
     //NXLogError ("Loaded %s.\n", path);
     //[self display]; NXPing();

     // free the extra windows we brought along
     [z_infoWindow setContentView:nil];
     [z_infoWindow free];
     [z_cameraWindow setContentView:nil];
     [z_cameraWindow free];
     [z_shapeWindow setContentView:nil];
     [z_shapeWindow free];
     [z_lightWindow setContentView:nil];
     [z_lightWindow free];
     [z_tclWindow setContentView:nil];
     [z_tclWindow free];
     [z_ribWindow setContentView:nil];
     [z_ribWindow free];

     // our surface shader matrix needs to be put into the lower left corner of the appropriate scrollView
    
     [suParmScrollView setBorderType:NX_BEZEL];
     [suParmScrollView setVertScrollerRequired:YES];
     [suParmScrollView setHorizScrollerRequired:NO];
     [suParmScrollView setAutoresizeSubviews:YES];
     [suParmScrollView getFrame:&scrollRect];

     // we need to set up the docView of the ClipView to contain both
     // the Matrix that we'll stick in plus the UI objects to manipulate 
     // the args in the Matrix.
     [ScrollView getContentSize:&(contentRect.size) forFrameSize:&(scrollRect.size) horizScroller:NO vertScroller:YES borderType:NX_BEZEL];
     contentRect.origin.x = contentRect.origin.y = 0.0;
     [suParmScrollView setDocView:[[View alloc] initFrame:&contentRect]];

     // add the Matrix to the docView of the scrollView
     [suParmNameMatrix getFrame:&matrixRect];
     matrixRect.origin.x = matrixRect.origin.y = 0.0;
     [suParmNameMatrix setFrame:&matrixRect];
     [[suParmScrollView docView] addSubview:suParmNameMatrix];

     suArgViewList = [[List alloc] init];

     // our displacement shader matrix needs to be put into the lower left corner of the appropriate scrollView
     [diParmScrollView setBorderType:NX_BEZEL];
     [diParmScrollView setVertScrollerRequired:YES];
     [diParmScrollView setHorizScrollerRequired:NO];
     [diParmScrollView setAutoresizeSubviews:YES];
     [diParmScrollView getFrame:&scrollRect];

     // we need to set up the docView of the ClipView to contain both
     // the Matrix that we'll stick in plus the UI objects to manipulate 
     // the args in the Matrix.
     [ScrollView getContentSize:&(contentRect.size) forFrameSize:&(scrollRect.size) horizScroller:NO vertScroller:YES borderType:NX_BEZEL];
     contentRect.origin.x = contentRect.origin.y = 0.0;
     [diParmScrollView setDocView:[[View alloc] initFrame:&contentRect]];

     // add the Matrix to the docView of the scrollView
     [diParmNameMatrix getFrame:&matrixRect];
     matrixRect.origin.x = matrixRect.origin.y = 0.0;
     [diParmNameMatrix setFrame:&matrixRect];
     [[diParmScrollView docView] addSubview:diParmNameMatrix];

     diArgViewList = [[List alloc] init];

     // light inspector stuff...  
     [distantLightView setImageFile:"distantLight"];
     [distantLightView setBorderType:1];
     [distantLightView setBackgroundColor:NXConvertRGBToColor(0.373, 0.298, 0.373)]; 
     [distantLightView setBackgroundAlpha:1.0];
     [distantLightView setLightType:N3D_DistantLight];
     //[distantLightView display];

     [pointLightView setImageFile:"pointLight"];
     [pointLightView setBorderType:1];
     [pointLightView setBackgroundColor:NXConvertRGBToColor(0.373, 0.298, 0.373)]; 
     [pointLightView setBackgroundAlpha:1.0];
     [pointLightView setLightType:N3D_PointLight];
     //[pointLightView display];

     [spotLightView setImageFile:"spotLight"];
     [spotLightView setBorderType:1];
     [spotLightView setBackgroundColor:NXConvertRGBToColor(0.373, 0.298, 0.373)]; 
     [spotLightView setBackgroundAlpha:1.0];
     [spotLightView setLightType:N3D_SpotLight];
     //[spotLightView display];

     // shape stuff
     // red:   255 81 107 == 1.00  .318 .420 
     // green: 96 215 121 ==  .376 .843 .475
     // blue:  98 124 255 ==  .384 .486 1.00

     // need to set the interp input text fields so that they 
     // have the right UI behavior
     [shapeInterpTextField setNextText:shapeInterpTextField];
     [cameraInterpTextField setNextText:cameraInterpTextField];

     // finally, make ourselves the delegate of the wellControlPanel,
     // so that when we get a performClose:, we know to nil out the outlet...
     [wellControlPanel setDelegate:self];
  }
  [ribParser setStatusText:statusText];
  [camera setStatusText:statusText];
  [camera setCurrentShape:[camera worldShape]];
  [shapeBrowser setTitled:NO];
  [[shapeBrowser loadColumnZero] selectRootShape];

  // okay, revert!
  [self revertControlPanel];
  [wellControlPanel display];
  [wellControlPanel makeKeyAndOrderFront:sender];
  [[camera worldShape] setVisible:YES];
  // display myself so that we see the shape again
  [self display];  NXPing();

  return self;
}

- mouseDown:(NXEvent *)theEvent  
{ edgeClicked = YES; 
  [statusText setStringValue:"loading control panel..."]; [statusText display]; NXPing();
  [self showControlPanel:nil]; 
  [statusText setStringValue:"done"]; [statusText display]; NXPing();
  return self;
}

- removeDefaultLights:sender { [camera removeDefaultLights]; [camera display]; return self; } 
- restoreDefaultLights:sender { [camera restoreDefaultLights]; [camera display]; return self; } 

- setRenderStartTime
{
  gettimeofday(&renderStartTime, 0);
  return self;
}

- camera:theCamera didRenderStream:(NXStream *)imageStream tag:(int)jobTag frameNumber:(int)currentFrame
{
  id           newImage;
  long         clock, tmpUsec;
  struct
      timeval  elapsedTime;
  NXRect  imageRect;
  NXSize  windowSize;


  NXLogError("camera's delegate got called for renderJobTag %d\n", jobTag);
  gettimeofday(&renderFinishTime, 0);
  time(&clock);
  sprintf(timeBuf, "rendering job %d finished at %s", jobTag, ctime(&clock));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();

  [self showImageView:nil];
  if (imageView)
  {  newImage = [[NXImage alloc] initFromStream:imageStream];
     [imageView setImage:newImage];
     [imageView sizeToImage:nil];
     [imageView getFrame:&imageRect];
     [[imageView superview] sizeTo:imageRect.size.width :imageRect.size.height];
     //windowSize.width = imageRect.size.width + 2;  // jeez, there must be a way to have the window do this...
     //windowSize.height = imageRect.size.height + 24;  // boy, this is really dumb...
     windowSize.width = imageRect.size.width;
     windowSize.height = imageRect.size.height;
     [imageViewWindow sizeWindow:windowSize.width :windowSize.height];
     [imageViewWindow setMinSize:&windowSize];
     [imageViewWindow setMaxSize:&windowSize];
     [imageViewWindow setTitle:[imageView name]];
     [imageView sizeToImage:nil];
     [imageViewWindow makeKeyAndOrderFront:nil];
  }

  elapsedTime.tv_sec = renderFinishTime.tv_sec  - renderStartTime.tv_sec;
  tmpUsec = renderFinishTime.tv_usec - renderStartTime.tv_usec;
  if (tmpUsec < 0)
  {  elapsedTime.tv_sec--;
     elapsedTime.tv_usec = (long)1000000 + tmpUsec;
  }
  else
  {  elapsedTime.tv_usec = tmpUsec;
  }
  if (elapsedTime.tv_usec > (long)1000000)
  {  elapsedTime.tv_sec++;
     elapsedTime.tv_usec -= (long)1000000;
  }
  
  sprintf(timeBuf, 
	  "rendering took %d.%d seconds\n", 
	  (int)(elapsedTime.tv_sec), (int)(1000000 - elapsedTime.tv_usec));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();

  return self;
}

- renderItUsing3DKit:sender
{
  long clock;


  // so here's the idea: if the camera's got a crop/selection window
  // up, we use that, otherwise we want to render the whole thing.
  //

  [camera setBackgroundRenderingOff];
  time(&clock);
  sprintf(timeBuf, "rendering started at %s", ctime(&clock));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
  }
  [renderHistoryText display];
  NXPing();
  gettimeofday(&renderStartTime, 0);
  renderJobTag = [camera renderAsTIFF];
  NXLogError("my renderJobTag is %d\n", renderJobTag);

  return self;
}



// really should make this be the rendering command and the file be variables, so you could do something like:
// system("rsh yoshi /usr/local/prman/bin/prman /cga/tmp/foo.rib")
// actually, it should be a thread off by itself.  If Springer had made this easy in the first place, argh....

- renderItBlocking:sender
{
  long         clock, tmpUsec;
  struct
      timeval  elapsedTime;
  char         tmpRIBFilename[64], tmpTIFFFilename[64];
  char         cmd[128], windowTitle[256];
  //id           aRenderWrangler;
  NXRect  imageRect;
  NXSize  windowSize;
  int     ret;


  [camera setBackgroundRenderingOff];
  // so here's the idea: if the camera's got a crop/selection window
  // up, we use that, otherwise we want to render the whole thing.
  //
  sprintf(tmpRIBFilename, "/tmp/foo%d.rib", getpid());
  sprintf(tmpTIFFFilename, "/tmp/foo%d.tiff", getpid());
  [camera dumpRIBToFile:tmpRIBFilename];
  time(&clock);
  sprintf(timeBuf, "rendering started at %s", ctime(&clock));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();
  gettimeofday(&renderStartTime, 0);

  if ([self useRendribInstead])
  {  sprintf(cmd, "rendrib %s", tmpRIBFilename);
  }
  else
  {  sprintf(cmd, "/usr/prman/prman %s", tmpRIBFilename);
  }

  ret = system(cmd);
  if (ret)
  {  sprintf(timeBuf, "rendering finished at %s\nwith an error of %d (check Workspace's Console for more details.\n", ctime(&clock), ret);
     if (renderHistoryText)
     {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
        [renderHistoryText setSelColor:NX_COLORRED];
        [renderHistoryText replaceSel:timeBuf];
        [renderHistoryText scrollSelToVisible];
     }
     [renderHistoryText display];
     NXBeep(); NXBeep();
     NXPing();
     return nil;
  }

  //aRenderWrangler = [[WWRenderWrangler alloc] init];
  //[aRenderWrangler setCmd:cmd];
  //[aRenderWrangler setBoss:self];
  //cthread_detach(cthread_fork(renderItDude, aRenderWrangler));

  gettimeofday(&renderFinishTime, 0);
  time(&clock);
  sprintf(timeBuf, "rendering finished at %s", ctime(&clock));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();

  elapsedTime.tv_sec = renderFinishTime.tv_sec  - renderStartTime.tv_sec;
  tmpUsec = renderFinishTime.tv_usec - renderStartTime.tv_usec;
  if (tmpUsec < 0)
  {  elapsedTime.tv_sec--;
     elapsedTime.tv_usec = (long)1000000 + tmpUsec;
  }
  else
  {  elapsedTime.tv_usec = tmpUsec;
  }
  if (elapsedTime.tv_usec > (long)1000000)
  {  elapsedTime.tv_sec++;
     elapsedTime.tv_usec -= (long)1000000;
  }
  
  // open up the proto nib with a movieView in it.
  [self showImageView:nil];

  [imageView setImage:[[NXImage alloc] initFromFile:tmpTIFFFilename]];
  [imageView sizeToImage:nil];
  [imageView getFrame:&imageRect];
  [[imageView superview] sizeTo:imageRect.size.width :imageRect.size.height];
  //windowSize.width = imageRect.size.width + 2;  // jeez, there must be a way to have the window do this...
  //windowSize.height = imageRect.size.height + 24;  // boy, this is really dumb...
  windowSize.width = imageRect.size.width;
  windowSize.height = imageRect.size.height;
  [imageViewWindow sizeWindow:windowSize.width :windowSize.height];
  [imageViewWindow setMinSize:&windowSize];
  [imageViewWindow setMaxSize:&windowSize];

  if ([self useRendribInstead])
  {  sprintf(windowTitle, "(%d.%dsec w/rendrib)", 
	    (int)(elapsedTime.tv_sec), (int)((1000000 - elapsedTime.tv_usec)/100000));
  }
  else
  {  sprintf(windowTitle, "(%d.%dsec@%3.2fshadingRate)", 
	    (int)(elapsedTime.tv_sec), (int)((1000000 - elapsedTime.tv_usec)/100000), [self shadingRate]);
  }
  [imageViewWindow setTitle:windowTitle];
  [imageView sizeToImage:nil];
  [imageViewWindow makeKeyAndOrderFront:nil];

  sprintf(timeBuf, 
	  "rendering took %d.%d seconds\n", 
	  (int)(elapsedTime.tv_sec), (int)((1000000 - elapsedTime.tv_usec)/100000));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  if (beepWhenDone) { NXBeep(); }
  NXPing();
  return self;
}

- saveImage:sender  { return [camera saveImage:sender]; }

- dumpRIB:sender  { return [camera dumpRIB:sender]; }

- dumpShot:sender  { return [camera dumpShotToRIB:sender]; }

- dumpEve:sender  { return [camera dumpEve:sender]; }

- dumpScene:sender  { return [camera dumpScene:sender]; }

- dump3DTextScene:sender  { return [camera dump3DTextScene:sender]; }

- dumpInventor:sender  { return [camera dumpInventor:sender]; }

- dumpSceneGraph:sender
{
  // this one, I'm gonna do

  // First of all, we need to find out the "oldest" sample in the current scene.
  //  

  return self;
}


// really should make this be the rendering command and the file be variables, so you could do something like:
// system("rsh yoshi /usr/local/prman/bin/prman /cga/tmp/foo.rib")

- renderShotBlocking:sender
{
  long         clock, tmpUsec;
  struct
      timeval  elapsedTime;
  char         tmpAnimDir[128], tmpRIBFilename[128];
  char         cmd[128];


  [camera setBackgroundRenderingOff];
  // so here's the idea: if the camera's got a crop/selection window
  // up, we use that, otherwise we want to render the whole thing.
  //
  sprintf(tmpAnimDir, "/tmp/foo%d.anim", getpid());
  sprintf(cmd, "mkdirs %s", tmpAnimDir);
  system(cmd);
  sprintf(tmpRIBFilename, "%s/foo%d.rib", tmpAnimDir, getpid());
  [camera dumpShotToRIBFile:tmpRIBFilename];
  time(&clock);
  sprintf(timeBuf, "rendering started at %s", ctime(&clock));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();
  gettimeofday(&renderStartTime, 0);

  sprintf(cmd, "/usr/prman/prman %s", tmpRIBFilename);


  // at this point, we want to fork a thread to actually render this guy.

  system(cmd);
  //cthread_detach(cthread_fork(evalCmdUsingSystem, cmd));


  gettimeofday(&renderFinishTime, 0);
  time(&clock);
  sprintf(timeBuf, "rendering finished at %s", ctime(&clock));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();
  [self showImageView:nil];
  [imageView setAnimDir:tmpAnimDir];

  elapsedTime.tv_sec = renderFinishTime.tv_sec  - renderStartTime.tv_sec;
  tmpUsec = renderFinishTime.tv_usec - renderStartTime.tv_usec;
  if (tmpUsec < 0)
  {  elapsedTime.tv_sec--;
     elapsedTime.tv_usec = (long)1000000 + tmpUsec;
  }
  else
  {  elapsedTime.tv_usec = tmpUsec;
  }
  if (elapsedTime.tv_usec > (long)1000000)
  {  elapsedTime.tv_sec++;
     elapsedTime.tv_usec -= (long)1000000;
  }
  
  sprintf(timeBuf, 
	  "rendering took %d.%d seconds\n", 
	  (int)(elapsedTime.tv_sec), (int)(1000000 - elapsedTime.tv_usec));
  if (renderHistoryText)
  {  [renderHistoryText setSel:[renderHistoryText textLength] :[renderHistoryText textLength]];
     [renderHistoryText setSelGray:NX_BLACK];
     [renderHistoryText replaceSel:timeBuf];
     [renderHistoryText scrollSelToVisible];
  }
  [renderHistoryText display];
  NXPing();
  return self;
}

//
- addShape:sender
{
  id           theOpenPanel;
  const char   *filename;
  char         *extension, 
               *filterTypes[6] = {"eve", "rib", "mdl", "wwModel", "wwScene", NULL };


  theOpenPanel = [OpenPanel new];
  [theOpenPanel allowMultipleFiles:NO];
  if (![theOpenPanel runModalForTypes:filterTypes])
  {  return nil;
  }

  filename = [theOpenPanel filename];
  extension = (char *)filename + (strlen(filename) - 4);
  if ((filename) && (!strcmp(".rib", extension)))
  {  [self addToShapeHierarchyFromRIBFile:filename];
  }
  if ((filename) && (!strcmp(".eve",  extension)))
  {  [self addToShapeHierarchyFromEveFile:filename];
  }
  if ((filename) && (!strcmp(".mdl",  extension)))
  {  [self addToShapeHierarchyFromMdlFile:filename];
  }

  extension = (char *)filename + (strlen(filename) - 8);
  if ((filename) && (!strcmp(".wwModel",  extension)))
  {  [self addToShapeHierarchyFromMdlFile:filename];
  }
  if ((filename) && (!strcmp(".wwScene",  extension)))
  {  [self addToShapeHierarchyFromSceneFile:filename];
  }
  return self;
}
//
- replaceWorldShape:sender
{
  id           theOpenPanel;
  const char   *filename;
  char         *extension, 
               *filterTypes[6] = {"eve", "rib", "mdl", "wwModel", "wwScene", NULL };


  theOpenPanel = [OpenPanel new];
  [theOpenPanel allowMultipleFiles:NO];
  if (![theOpenPanel runModalForTypes:filterTypes])
  {  return nil;
  }

  filename = [theOpenPanel filename];
  extension = (char *)filename + (strlen(filename) - 4);
  if ((filename) && (!strcmp(".rib", extension)))
  {  [self buildNewShapeHierarchyFromRIBFile:filename];
  }
  if ((filename) && (!strcmp(".eve",  extension)))
  {  [self buildNewShapeHierarchyFromEveFile:filename];
  }
  if ((filename) && (!strcmp(".mdl",  extension)))
  {  [self buildNewShapeHierarchyFromMdlFile:filename];
  }

  extension = (char *)filename + (strlen(filename) - 8);
  if ((filename) && (!strcmp(".wwModel",  extension)))
  {  [self addToShapeHierarchyFromMdlFile:filename];
  }
  if ((filename) && (!strcmp(".wwScene",  extension)))
  {  [self addToShapeHierarchyFromSceneFile:filename];
  }

  return self;
}

- loadNibForModelInterp:(const char *)nibFilename  
{  return [[ribParser tclInterp] loadControlPanel:(char *)nibFilename modally:NO];
}

- loadNibForCameraInterp:(const char *)nibFilename  
{  return [[camera tclInterp] loadControlPanel:(char *)nibFilename modally:NO];
}

- revertSurfaceShaderInspectorFor:s
{
  int       i, howManyCells, howManyArgs;
  RtPoint   pValue;
  float     fValue;
  NXColor   cValue;
  char      *sValue;
  SLO_TYPE  argType, shaderType;
  id        newCell, newArgView;
  NXRect    argViewFrame, matrixFrame, superFrame;
  NXSize    cellSize, intercellSize;


  howManyArgs = [s shaderArgCount];
  shaderType = [s shaderType];

  [suNameText setStringValue:[s shader]];  

  // get the scrollView's docView
  // need to make a  
  suParmView = [suParmScrollView docView];

  [suParmNameMatrix setAutoscroll:YES];
  [suParmNameMatrix setScrollable:YES];
  
  [suParmNameMatrix getFrame:&matrixFrame];
  [suParmNameMatrix getCellSize:&cellSize];
  [suParmNameMatrix getIntercell:&intercellSize];
  // don't want any intercell spacing between arg views
  argViewFrame.size.width =  cellSize.width - 1;
  argViewFrame.size.height =  cellSize.height + intercellSize.height;
  argViewFrame.origin.x =  matrixFrame.size.width; 
  argViewFrame.origin.y =  (howManyArgs - 1) * argViewFrame.size.height;

  // clear things out...
  howManyCells = [[suParmNameMatrix cellList] count];
  for (i = 0; i < howManyCells; i++)
  {  [suParmNameMatrix removeRowAt:0 andFree:YES];
     [suParmNameMatrix sizeToCells];
  }
  [suArgViewList freeObjects];

  // need to tell the Matrix's superview to resize itself to (howManyArgs*argViewFrame.size.height)
  [[suParmNameMatrix superview] getFrame:&superFrame];
  [[suParmNameMatrix superview] sizeTo:superFrame.size.width :(howManyArgs*argViewFrame.size.height)];

  for (i = 0; i < howManyArgs; i++)
  {  // build up the new one...
     [suParmNameMatrix addRow];
     [suParmNameMatrix sizeToCells];
     newCell = [suParmNameMatrix cellAt:i :0];
     [newCell setStringValue:[s shaderArgNameAt:i]];
     [newCell setAlignment:NX_RIGHTALIGNED];

     // now we need to build up the args...
     argType = [s shaderArgType:[s shaderArgNameAt:i]];
     switch (argType) {
	 case SLO_TYPE_POINT:   [s getShaderArg:[s shaderArgNameAt:i] pointValue:&pValue];
                                NXLogError("\targName:%s type:point value:(%f %f %f)\n", 
                                           [s shaderArgNameAt:i], N3D_XComp(pValue), N3D_YComp(pValue), N3D_ZComp(pValue)); 
                                break;
	 case SLO_TYPE_COLOR:   [s getShaderArg:[s shaderArgNameAt:i] colorValue:&cValue];
                                newArgView = [[WWShaderArgColorWell alloc] initFrame:&argViewFrame];
                                [newArgView setShaderArgName:[s shaderArgNameAt:i]];
                                [newArgView setTarget:s];
                                [newArgView setAction:@selector(takeColorArg:)];
                                [newArgView setColor:cValue];
                                [suParmView addSubview:newArgView];
                                [suArgViewList addObject:newArgView];
                                break;
	 case SLO_TYPE_SCALAR:  [s getShaderArg:[s shaderArgNameAt:i] floatValue:&fValue];
                                newArgView = [[WWShaderArgTextField alloc] initFrame:&argViewFrame];
                                [newArgView setShaderArgName:[s shaderArgNameAt:i]];
                                [newArgView setTarget:s];
                                [newArgView setAction:@selector(takeFloatArg:)];
                                [newArgView setFloatValue:fValue];
                                [suParmView addSubview:newArgView];
                                [suArgViewList addObject:newArgView];
                                break;
	 case SLO_TYPE_STRING:  [s getShaderArg:[s shaderArgNameAt:i] stringValue:&sValue];
                                newArgView = [[WWShaderArgTextField alloc] initFrame:&argViewFrame];
                                [newArgView setShaderArgName:[s shaderArgNameAt:i]];
                                [newArgView setTarget:s];
                                [newArgView setAction:@selector(takeStringArg:)];
                                [newArgView setStringValue:sValue];
                                [suParmView addSubview:newArgView];
                                [suArgViewList addObject:newArgView];
                                break;
	 default:               NXLogError("\targName:%s type:UNKNOWN\n", [s shaderArgNameAt:i]);
                                break;
      }
      argViewFrame.origin.y -=  argViewFrame.size.height;
  }
  [suParmView display];

  

  return self;
}

- revertDisplacementShaderInspectorFor:s
{
  int       i, howManyCells, howManyArgs;
  RtPoint   pValue;
  float     fValue;
  NXColor   cValue;
  char      *sValue;
  SLO_TYPE  argType, shaderType;
  id        newCell, newArgView;
  NXRect    argViewFrame, matrixFrame, superFrame;
  NXSize    cellSize, intercellSize;


  howManyArgs = [s shaderArgCount];
  shaderType = [s shaderType];

  [diNameText setStringValue:[s shader]];  

  // get the scrollView's docView
  // need to make a  
  diParmView = [diParmScrollView docView];

  [diParmNameMatrix setAutoscroll:YES];
  [diParmNameMatrix setScrollable:YES];
  
  [diParmNameMatrix getFrame:&matrixFrame];
  [diParmNameMatrix getCellSize:&cellSize];
  [diParmNameMatrix getIntercell:&intercellSize];
  // don't want any intercell spacing between arg views
  argViewFrame.size.width =  cellSize.width - 1;
  argViewFrame.size.height =  cellSize.height + intercellSize.height;
  argViewFrame.origin.x =  matrixFrame.size.width; 
  argViewFrame.origin.y =  (howManyArgs - 1) * argViewFrame.size.height;

  // clear things out...
  howManyCells = [[diParmNameMatrix cellList] count];
  for (i = 0; i < howManyCells; i++)
  {  [diParmNameMatrix removeRowAt:0 andFree:YES];
     [diParmNameMatrix sizeToCells];
  }
  [diArgViewList freeObjects];

  // need to tell the Matrix's superview to resize itself to (howManyArgs*argViewFrame.size.height)
  [[diParmNameMatrix superview] getFrame:&superFrame];
  [[diParmNameMatrix superview] sizeTo:superFrame.size.width :(howManyArgs*argViewFrame.size.height)];

  for (i = 0; i < howManyArgs; i++)
  {  // build up the new one...
     [diParmNameMatrix addRow];
     [diParmNameMatrix sizeToCells];
     newCell = [diParmNameMatrix cellAt:i :0];
     [newCell setStringValue:[s shaderArgNameAt:i]];
     [newCell setAlignment:NX_RIGHTALIGNED];

     // now we need to build up the args...
     argType = [s shaderArgType:[s shaderArgNameAt:i]];
     switch (argType) {
	 case SLO_TYPE_POINT:   [s getShaderArg:[s shaderArgNameAt:i] pointValue:&pValue];
                                NXLogError("\targName:%s type:point value:(%f %f %f)\n", 
                                           [s shaderArgNameAt:i], N3D_XComp(pValue), N3D_YComp(pValue), N3D_ZComp(pValue)); 
                                break;
	 case SLO_TYPE_COLOR:   [s getShaderArg:[s shaderArgNameAt:i] colorValue:&cValue];
                                newArgView = [[WWShaderArgColorWell alloc] initFrame:&argViewFrame];
                                [newArgView setShaderArgName:[s shaderArgNameAt:i]];
                                [newArgView setTarget:s];
                                [newArgView setAction:@selector(takeColorArg:)];
                                [newArgView setColor:cValue];
                                [diParmView addSubview:newArgView];
                                [diArgViewList addObject:newArgView];
                                break;
	 case SLO_TYPE_SCALAR:  [s getShaderArg:[s shaderArgNameAt:i] floatValue:&fValue];
                                newArgView = [[WWShaderArgTextField alloc] initFrame:&argViewFrame];
                                [newArgView setShaderArgName:[s shaderArgNameAt:i]];
                                [newArgView setTarget:s];
                                [newArgView setAction:@selector(takeFloatArg:)];
                                [newArgView setFloatValue:fValue];
                                [diParmView addSubview:newArgView];
                                [diArgViewList addObject:newArgView];
                                break;
	 case SLO_TYPE_STRING:  [s getShaderArg:[s shaderArgNameAt:i] stringValue:&sValue];
                                newArgView = [[WWShaderArgTextField alloc] initFrame:&argViewFrame];
                                [newArgView setShaderArgName:[s shaderArgNameAt:i]];
                                [newArgView setTarget:s];
                                [newArgView setAction:@selector(takeStringArg:)];
                                [newArgView setStringValue:sValue];
                                [diParmView addSubview:newArgView];
                                [diArgViewList addObject:newArgView];
                                break;
	 default:               NXLogError("\targName:%s type:UNKNOWN\n", [s shaderArgNameAt:i]);
                                break;
      }
      argViewFrame.origin.y -=  argViewFrame.size.height;
  }
  [diParmView display];

  return self;
}

- takeSuColor:sender
{
  [[[camera currentShape] shaderType:SLO_TYPE_SURFACE] setColor:[sender color]];
  return self;
}

- takeDiColor:sender
{
  [[[camera currentShape] shaderType:SLO_TYPE_DISPLACEMENT] setColor:[sender color]];
  return self;
}

- drawRtMatrix:(RtMatrix)anRtMatrix inMatrix:aMatrix
{
  int  i, j;


  for (i = 0; i < 4; i++)
  {  for (j = 0; j < 4; j++)
     {  [[aMatrix cellAt:i :j] setFloatValue:anRtMatrix[i][j]];
     }
  }
  return self;
}

- fillRIBCommandsMatrix:theMatrix
{
   int         howMany, i;
   id          newCell;
   const char  *className;


   howMany = [[theMatrix cellList] count];
   for (i = 0; i < howMany; i++)
   {  [theMatrix removeRowAt:0 andFree:YES];
      [theMatrix sizeToCells];
   }
   howMany = [[[camera currentShape] ribCommands] count];
   for (i = 0; i < howMany; i++)
   {  [theMatrix addRow];
      [theMatrix sizeToCells];
      newCell = [theMatrix cellAt:i :0];
//Note: these really should be double-clickable themselves...
      // need to find out the class name of the cell
      // if it's an EveCommand, we need to get the class name of the firstSample
      // we'll enclose that in ()
      // if it's an EveProc, we need to get the name of the proc
      // we'll enclose that in {}
      className = [[[[[camera currentShape] ribCommands] objectAt:i] class] name];
      if (!strcmp(className, "EveCommand") || !strcmp(className, "EveProc"))
      {  className = [[[[camera currentShape] ribCommands] objectAt:i] sampleName];
      }
      [newCell setStringValue:className];
   }

   return self;
}

//
- removeSurfaceShader:sender 
{ 
   [[camera currentShape] removeSurfaceShader:sender]; 
   [self revertSurfaceShaderInspectorFor:nil];
   [delegate sceneWasUpdated:camera];
   [self display];
   return self;
}

- removeDisplacementShader:sender 
{ 
   [[camera currentShape] removeDisplacementShader:sender]; 
   [self revertDisplacementShaderInspectorFor:nil];
   [delegate sceneWasUpdated:camera];
   [self display];
   return self;
}

- addShader:sender
{
  id           theOpenPanel;
  const char   *filename;
  char         *extension, 
               *filterTypes[3] = {"slo", "WW3DShader", NULL };
  id            newShader;
  static char   shaderName[256];


  theOpenPanel = [OpenPanel new];
  [theOpenPanel allowMultipleFiles:NO];
  if (![theOpenPanel runModalForDirectory:"/LocalLibrary/Shaders" file:NULL types:filterTypes])
  {  return self;
  }

  filename = [theOpenPanel filename];
  extension = (char *)filename + (strlen(filename) - 4);
  if ((filename) && (!strcmp(".slo",  extension)))
  {  basename(filename, ".slo", shaderName);
     if ((newShader = [(WW3DShader *)[[WW3DShader alloc] init] setShader:shaderName]))
     {  [[camera currentShape] setShader_:newShader];
     }

     // at this point, we want to ask the shader to describe its
     // arguments to us, and construct an inspector for it which gets put on
     // it the control panel
     if ([newShader shaderType] == SLO_TYPE_SURFACE)
     {  [self revertSurfaceShaderInspectorFor:newShader];
     }
     if ([newShader shaderType] == SLO_TYPE_DISPLACEMENT)
     {  [self revertDisplacementShaderInspectorFor:newShader];
     }

     [delegate sceneWasUpdated:camera];
     [self display];
  }
  else
  {  return nil;
  }
  return self;
}

- addSurfaceShader:sender
{
  id           theOpenPanel;
  const char   *filename;
  char         *extension, 
               *filterTypes[3] = {"slo", "WW3DShader", NULL };
  id            newShader;
  static char   shaderName[256];


  theOpenPanel = [OpenPanel new];
  [theOpenPanel allowMultipleFiles:NO];
  if (![theOpenPanel runModalForDirectory:"/LocalLibrary/Shaders" file:NULL types:filterTypes])
  {  return self;
  }

  filename = [theOpenPanel filename];
  extension = (char *)filename + (strlen(filename) - 4);
  if ((filename) && (!strcmp(".slo",  extension)))
  {  basename(filename, ".slo", shaderName);
     if (!(newShader = [(WW3DShader *)[[WW3DShader alloc] init] setShader:shaderName]))
     {  return nil;
     }

     // at this point, we want to ask the shader to describe its
     // arguments to us, and construct an inspector for it which gets put on
     // it the control panel
     if ([newShader shaderType] == SLO_TYPE_SURFACE)
     {  [[camera currentShape] setShader_:newShader];
        [self revertSurfaceShaderInspectorFor:newShader];
        [delegate sceneWasUpdated:camera];
        [self display];
     }
     else
     {  NXLogError("%s is not a surface shader\n", shaderName);
	[newShader free];
	return nil;
     }
  }
  else
  {  return nil;
  }
  return self;
}

- addDisplacementShader:sender
{
  id           theOpenPanel;
  const char   *filename;
  char         *extension, 
               *filterTypes[3] = {"slo", "WW3DShader", NULL };
  id            newShader;
  static char   shaderName[256];


  theOpenPanel = [OpenPanel new];
  [theOpenPanel allowMultipleFiles:NO];
  if (![theOpenPanel runModalForDirectory:"/LocalLibrary/Shaders" file:NULL types:filterTypes])
  {  return self;
  }

  filename = [theOpenPanel filename];
  extension = (char *)filename + (strlen(filename) - 4);
  if ((filename) && (!strcmp(".slo",  extension)))
  {  basename(filename, ".slo", shaderName);
     if (!(newShader = [(WW3DShader *)[[WW3DShader alloc] init] setShader:shaderName]))
     {  return nil;
     }

     // at this point, we want to ask the shader to describe its
     // arguments to us, and construct an inspector for it which gets put on
     // it the control panel
     if ([newShader shaderType] == SLO_TYPE_DISPLACEMENT)
     {  [[camera currentShape] setShader_:newShader];
        [self revertDisplacementShaderInspectorFor:newShader];
        [delegate sceneWasUpdated:camera];
        [self display];
     }
     else
     {  NXLogError("%s is not a displacement shader\n", shaderName);
	[newShader free];
	return nil;
     }
  }
  else
  {  return nil;
  }
  return self;
}


// inspector/control panel methods
//
- (RtFloat)tesselation  { return [camera tesselation]; }
- takeTesselation:sender  { [camera takeTesselation:sender];  return [self revertControlPanel]; }
//
- (NXColor)backgroundColor  { return [camera backgroundColor]; }
- takeWellBackgroundColor:sender  { [camera setBackgroundColor:[sender color]];  return [self revertControlPanel]; }
//
- (RtFloat)lowRezTesselation  { return [camera lowRezTesselation]; }
- takeLowRezTesselation:sender  {  [camera takeLowRezTesselation:sender];   return [self revertControlPanel]; }
//
- (RtFloat)shadingRate { return [camera shadingRate]; }
// don't bother reverting the control panel for this one
- takeShadingRate:sender 
{  [camera takeShadingRate:sender]; 
   [delegate sceneWasUpdated:camera]; 
   // rather than revert the whole control panel, just do these two...
   [shadingRateText setFloatValue:[camera shadingRate]];
   [shadingRateSlider setFloatValue:[camera shadingRate]];
   return self; 
}
//
- (BOOL)firmTransforms {  return [ribParser firmTransforms]; }
- takeFirmTransforms:sender {  [ribParser takeFirmTransforms:sender];  return [self revertControlPanel]; }
//
- (BOOL)ignoreShadingRate { return [ribParser ignoreShadingRate]; } 
- takeIgnoreShadingRate:sender { [ribParser setIgnoreShadingRate:sender];  return [self revertControlPanel]; }
//
- (BOOL)ignoreColors { return [ribParser ignoreColors]; } 
- takeIgnoreColors:sender { [ribParser setIgnoreColors:sender];  return [self revertControlPanel]; }
//
- (BOOL)ignoreShaders { return [ribParser ignoreShaders]; }
- takeIgnoreShaders:sender { [ribParser setIgnoreShaders:sender];  return [self revertControlPanel]; }
//
- (BOOL)ignoreLights { return [ribParser ignoreLights]; }
- takeIgnoreLights:sender { [ribParser setIgnoreLights:sender];  return [self revertControlPanel]; }
//
- (NXColor)selectedColor { return selectedColor; }
- takeSelectedColor:sender { return self; }
//
- (NXColor)unselectedColor { return unselectedColor; }
- takeUnselectedColor:sender { return self; }
//
- (float)fov { return [camera fieldOfView]; }
- takeFOV:sender 
{ float fov = [sender floatValue];

  if (fov < 0.0)
  {  fov = 0.0;
  }
  if (fov > 180.0)
  {  fov = 180.0;
  }
  [camera setFieldOfViewByAngle:fov]; 
  [self revertControlPanel];
  [delegate sceneWasUpdated:camera]; 
  [self display];
  return self; 
}
//
- (int)aspectRatioType { return aspectRatioType; }
- (float)aspectRatio { return aspectRatio; }
- setCustomAspectRatioText:newCustomAspectRatioText { customAspectRatioText = newCustomAspectRatioText; return self; }
- takeAspectRatioFromMatrix:sender
{
  NXRect  newRect;


  [self getFrame:&newRect];
  aspectRatioType = [[sender selectedCell] tag];
  switch (aspectRatioType)
  {  case WW_ASPECT_DONT_CARE:
            [customAspectRatioText setEnabled:NO];
            break;
     case WW_ASPECT_NTSC:  // 1.33:1
            [customAspectRatioText setEnabled:NO];
            newRect.size.height = newRect.size.width/1.33;
            break;
     case WW_ASPECT_AMERICAN_WIDESCREEN:  // 1.85:1
            [customAspectRatioText setEnabled:NO];
            newRect.size.height = newRect.size.width/1.85;
            break;
     case WW_ASPECT_EUROPEAN_WIDESCREEN:  // 1.66:1
            [customAspectRatioText setEnabled:NO];
            newRect.size.height = newRect.size.width/1.66;
            break;
     case WW_ASPECT_VISTA_VISION:  // 2.21:1
            [customAspectRatioText setEnabled:NO];
            newRect.size.height = newRect.size.width/2.21;
            break;
     case WW_ASPECT_SQUARE:  // 1:1
            [customAspectRatioText setEnabled:NO];
            if (newRect.size.width < newRect.size.height)
            {  newRect.size.height = newRect.size.width;
	    }
	    else
	    {  newRect.size.width = newRect.size.height;
	    }
            break;
     case WW_ASPECT_CUSTOM:  //aspectX:aspectY
            [customAspectRatioText setEnabled:YES];
            aspectRatio = [customAspectRatioText floatValue];
            if (aspectRatio < 1.0)
	    {  newRect.size.width = newRect.size.height * aspectRatio;
	    }
	    else
	    {  newRect.size.height = newRect.size.width/aspectRatio;
	    }
            break;
     default:
            [customAspectRatioText setEnabled:NO];
            NXLogError("unknown aspect ratio type: %d", aspectRatioType);
            break;
  }
  [self sizeTo:newRect.size.width :newRect.size.height];
  [delegate sceneWasUpdated:camera]; 
  [self revertControlPanel];

  return self;
}
//
- takeAspectRatio:sender
{
  NXRect  newRect;


  [self getFrame:&newRect];
  aspectRatio = [sender floatValue];

  if (aspectRatioType != WW_ASPECT_CUSTOM)
  {  [self revertControlPanel];
     return self;
  }
  if (aspectRatio < 1.0)
  {  newRect.size.width = newRect.size.height * aspectRatio;
  }
  else
  {  newRect.size.height = newRect.size.width/aspectRatio;
  }
  [self sizeTo:newRect.size.width :newRect.size.height];
  [delegate sceneWasUpdated:camera]; 
  [self revertControlPanel];

  return self;
}
//
- (N3DProjectionType)projectionType { return projectionType; }
- takeProjectionTypeFromMatrix:sender
{
  int  theProjectionType = [[sender selectedCell] tag];


  switch (theProjectionType)
  {  case N3D_Perspective:
            projectionType = theProjectionType;
            break;
     case N3D_Orthographic:
            projectionType = theProjectionType;
            break;
     default:
            NXLogError("unknown projection type: %d", theProjectionType);
            break;
  }
  [camera setProjection:projectionType];
  [delegate sceneWasUpdated:camera]; 
  [self revertControlPanel];
  [window display];

  return self;
}

- takeInitialGeometry:sender
{
  char *name = (char *)[sender stringValue], *extension;

  
  // must be at least x.rib or x.eve
  if (!*name || (strlen(name) < 5))
  {  return nil;
  }
  extension = name + (strlen(name) - 4);
  if (!strcmp(extension, ".eve"))
  {  return [self buildNewShapeHierarchyFromEveFile:name];
  }
  if (!strcmp(extension, ".rib"))
  {  return [self buildNewShapeHierarchyFromRIBFile:name];
  }

  return nil;
}

- takeInitialImage:sender  {  return [camera setImageFile:[sender stringValue]]; }
- takeInitialImageFile:(const char *)n  {  return [camera setImageFile:n]; }

- (int)trackballAffects { return [camera trackballAffects]; }
- takeTrackballAffectsFromMatrix:sender {  return [camera takeTrackballAffectsFromMatrix:sender]; }

- (int)trackballXYZ { return [camera trackballXYZ]; }
- takeTrackballXYZFromMatrix:sender {  return [camera takeTrackballXYZFromMatrix:sender]; }


// history stuff
- takeStatusText:sender  { statusText = sender; return [camera takeStatusText:sender]; }


// scene clock stuff
- takeSceneClockTime:sender  { [sceneClock setCurrentTime:[sender floatValue]]; return self; }
- takeSceneClockSamplesPerSecond:sender {  [sceneClock setSamplesPerSecond:[sender floatValue]]; return self; }
- takeSceneClockIncrement:sender  {  [sceneClock setIncrement:[sender floatValue]]; return self;  }
- takeSceneClockSkip:sender  {  [sceneClock setSkip:[sender floatValue]]; return self;  }
- takeSceneClockRatio:sender  {  [sceneClock setRatio:[sender floatValue]]; return self; }
- takeSceneClockMark:sender { [sceneClock takeMark:sender]; return self; }
  // scene clock informal protocol
- rewind:sender { [sceneClock rewind:sender]; return self; }
- pause:sender { [sceneClock pause:sender]; return self; }
- play:sender { [sceneClock play:sender]; return self; }
- increment:sender { [sceneClock increment:sender]; return self; }
- decrement:sender { [sceneClock decrement:sender]; return self; }
- fastForward:sender { [sceneClock fastForward:sender]; return self; }
- takeMark:sender { [sceneClock takeMark:sender]; return self; }
- findLastSampleAndSetMark:sender  {  [sceneClock setMark:[[camera worldShape] lastSampleIsAt]]; return self; }

////////////////////////////////////////////////////////////////////
//			Dragging stuff
///////////////////////////////////////////////////////////////////

- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
    Pasteboard    *pboard;
    char          *argv;
    int           len;
    const NXAtom  *theType;
    char          *extension;
    NXEvent       *theEvent = [NXApp currentEvent];
    

    pboard = [sender draggingPasteboard];
    theType = [pboard types];

    if (*theType != NXFilenamePboardType)
    {  if ([pboard readType:NXFilenamePboardType data:&argv length:&len] == nil)
       {  fprintf(stderr, "unable to read filename from pasteboard.\n"); fflush(stderr);
	  return NX_DragOperationNone;
       }
       else
       {  // has to be at least "x.rib" 
          extension = argv + (strlen(argv) - 4);
          if ((strlen(argv) > 5) && (!strcmp(extension, ".rib")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
 	     }
 	     else
             {  return NX_DragOperationGeneric;
 	     }
          }

          // maybe it's a "xxxx.eve" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".eve")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return  NX_DragOperationCopy;
             }
             else
             {  return NX_DragOperationGeneric;
             }
          }
          // maybe it's a "xxxx.mdl" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".mdl")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
             }
             else
             {  return  NX_DragOperationGeneric;
             }
          }

          // maybe it's a "xxxx.scn" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".scn")))
          {  return NX_DragOperationGeneric;
          }

          // maybe it's an .slo file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".slo")))
          {  return NX_DragOperationCopy;
	  }

          // maybe it's a .wwcam or .cam file          
          if ((strlen(argv) > 5) && (!strcmp(extension, ".cam")))
          {  return NX_DragOperationCopy;
          }
          
          // maybe it's a .cam.nib
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".cam.nib")))
          {  // we should set this shader as the shader for the currently selected shape
             return NX_DragOperationCopy;
          }

          // maybe it's a "xxxx.wwModel" file...
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".wwModel")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
             }
             else
             {  return NX_DragOperationGeneric;
             }
          }
          // maybe it's a "xxxx.wwScene" file...
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".wwScene")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
             }
             else
             {  return NX_DragOperationGeneric;
             }
          }

          // maybe it's an archived version of a WW3DShader object...
          extension = argv + (strlen(argv) - 11);
          if ((strlen(argv) > 12) && (!strcmp(extension, ".WW3DShader")))
          {  // we should set this shader as the shader for the currently selected shape
             fprintf(stderr, "should be setting current shape's shader to this unarchived one...\n");
             return NX_DragOperationNone;
          }
          
          // maybe it's a model template file...
          extension = argv + (strlen(argv) - 12);
          if ((strlen(argv) > 13) && (!strcmp(extension, ".mdlTemplate")))
          {  return NX_DragOperationCopy;
          }
          
          // maybe it's a .nib file...
          extension = argv + (strlen(argv) - 4);
          if ((strlen(argv) > 5) && (!strcmp(extension, ".nib")))
          {  // really should have some way of dropping it so the camera's interp 
             // gets it, but I guess that's what a .cam.nib is...
	     return NX_DragOperationCopy;
	  }
       }
    }
    return NX_DragOperationNone;
}



- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
    Pasteboard    *pboard;
    char          *argv;
    int           len;
    const NXAtom  *theType;
    char          *extension;
    NXEvent       *theEvent = [NXApp currentEvent];
    

    pboard = [sender draggingPasteboard];
    theType = [pboard types];

    if (*theType != NXFilenamePboardType)
    {  if ([pboard readType:NXFilenamePboardType data:&argv length:&len] == nil)
       {  fprintf(stderr, "unable to read filename from pasteboard.\n"); fflush(stderr);
	  return NX_DragOperationNone;
       }
       else
       {  // has to be at least "x.rib" 
          extension = argv + (strlen(argv) - 4);
          if ((strlen(argv) > 5) && (!strcmp(extension, ".rib")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
 	     }
 	     else
             {  return NX_DragOperationGeneric;
 	     }
          }

          // maybe it's a "xxxx.eve" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".eve")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return  NX_DragOperationCopy;
             }
             else
             {  return NX_DragOperationGeneric;
             }
          }
          // maybe it's a "xxxx.mdl" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".mdl")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
             }
             else
             {  return  NX_DragOperationGeneric;
             }
          }

          // maybe it's a "xxxx.scn" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".scn")))
          {  return NX_DragOperationGeneric;
          }

          // maybe it's an .slo file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".slo")))
          {  return NX_DragOperationCopy;
	  }

          // maybe it's a .wwcam or .cam file          
          if ((strlen(argv) > 5) && (!strcmp(extension, ".cam")))
          {  return NX_DragOperationCopy;
          }
          
          // maybe it's a .cam.nib
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".cam.nib")))
          {  // we should set this shader as the shader for the currently selected shape
             return NX_DragOperationCopy;
          }

          // maybe it's a "xxxx.wwModel" file...
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".wwModel")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
             }
             else
             {  return NX_DragOperationGeneric;
             }
          }
          // maybe it's a "xxxx.wwScene" file...
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".wwScene")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  return NX_DragOperationCopy;
             }
             else
             {  return NX_DragOperationGeneric;
             }
          }

          // maybe it's an archived version of a WW3DShader object...
          extension = argv + (strlen(argv) - 11);
          if ((strlen(argv) > 12) && (!strcmp(extension, ".WW3DShader")))
          {  // we should set this shader as the shader for the currently selected shape
             fprintf(stderr, "should be setting current shape's shader to this unarchived one...\n");
             return NX_DragOperationNone;
          }
          
          // maybe it's a model template file...
          extension = argv + (strlen(argv) - 12);
          if ((strlen(argv) > 13) && (!strcmp(extension, ".mdlTemplate")))
          {  return NX_DragOperationCopy;
          }
          
          // maybe it's a .nib file...
          extension = argv + (strlen(argv) - 4);
          if ((strlen(argv) > 5) && (!strcmp(extension, ".nib")))
          {  // really should have some way of dropping it so the camera's interp 
             // gets it, but I guess that's what a .cam.nib is...
	     return NX_DragOperationCopy;
	  }
       }
    }
    return NX_DragOperationNone;
}


- (BOOL)performDragOperation:(id <NXDraggingInfo>)sender { return YES; }

// do the real work here, as it might be awhile...
- concludeDragOperation:(id <NXDraggingInfo>)sender
{
    Pasteboard    *pboard;
    char          *argv;
    int           len, ret;
    const NXAtom  *theType;
    char          *extension;
    id            newShader;
    static char   shaderName[256];
    static char   cmdBuf[MAXPATHLEN + 32];
    NXEvent       *theEvent = [NXApp currentEvent];
    

    pboard = [sender draggingPasteboard];
    theType = [pboard types];

    if (*theType != NXFilenamePboardType)
    {  if ([pboard readType:NXFilenamePboardType data:&argv length:&len] == nil)
       {  fprintf(stderr, "unable to read filename from pasteboard.\n"); fflush(stderr);
	  return nil;
       }
       else
       {  // has to be at least "x.rib" 
          extension = argv + (strlen(argv) - 4);
          if ((strlen(argv) > 5) && (!strcmp(extension, ".rib")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  if (![self addToShapeHierarchyFromRIBFile:argv])
                {  return nil;
                }
                return self;
 	     }
 	     else
             {  if (![self buildNewShapeHierarchyFromRIBFile:argv])
                {  return nil;
                }
                return self;
 	     }
          }

          // maybe it's a "xxxx.eve" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".eve")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  if (![self addToShapeHierarchyFromEveFile:argv])
                {  return nil;
                }
                return self;
             }
             else
             {  if (![self buildNewShapeHierarchyFromEveFile:argv])
                {  return nil;
                }
                return self;
             }
          }
          // maybe it's a "xxxx.mdl" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".mdl")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  if (![self addToShapeHierarchyFromMdlFile:argv])
                {  return nil;
                }
                return self;
             }
             else
             {  if (![self buildNewShapeHierarchyFromMdlFile:argv])
                {  return nil;
                }
                return self;
             }
          }

          // maybe it's a "xxxx.scn" file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".scn")))
          {  if (![self buildNewShapeHierarchyFromSceneFile:argv])
             {  return nil;
             }
             return self;
          }

          // maybe it's an .slo file...
          if ((strlen(argv) > 5) && (!strcmp(extension, ".slo")))
          {  // we should set this shader as the shader for the currently selected shape
             fprintf(stderr, "setting current shape's shader...\n");
	     basename(argv, ".slo", shaderName);
	     newShader = [(WW3DShader *)[[WW3DShader alloc] init] setShader:shaderName];
	     if (!newShader)
	     {  return nil;
	     }
	     [[camera currentShape] setShader_:newShader];
	     return self;
	  }

          // maybe it's a .wwcam or .cam file          
          if ((strlen(argv) > 5) && (!strcmp(extension, ".cam")))
          {  // we should set this shader as the shader for the currently selected shape
             sprintf(cmdBuf, "source %s", argv);
             ret = [[camera tclInterp] eval:cmdBuf];
             if (ret != TCL_OK)
             {  char *errorInfo = Tcl_GetVar([[camera tclInterp] interp], "errorInfo", 0);
                NXLogError("problem sourcing file:\n%s", errorInfo);
                return nil;  
             }  
             return self;
          }
          
          // maybe it's a .cam.nib
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".cam.nib")))
          {  // we should set this shader as the shader for the currently selected shape
             [[camera tclInterp] loadControlPanel:argv modally:NO];
             return self;
          }

          // maybe it's a "xxxx.wwModel" file...
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".wwModel")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  if (![self addToShapeHierarchyFromMdlFile:argv])
                {  return nil;
                }
                return self;
             }
             else
             {  if (![self buildNewShapeHierarchyFromMdlFile:argv])
                {  return nil;
                }
                return self;
             }
          }
          // maybe it's a "xxxx.wwScene" file...
          extension = argv + (strlen(argv) - 8);
          if ((strlen(argv) > 9) && (!strcmp(extension, ".wwScene")))
          {  if (theEvent->flags & NX_ALTERNATEMASK)
             {  if (![self addToShapeHierarchyFromSceneFile:argv])
                {  return nil;
                }
                return self;
             }
             else
             {  if (![self buildNewShapeHierarchyFromSceneFile:argv])
                {  return nil;
                }
                return self;
             }
          }

          // maybe it's an archived version of a WW3DShader object...
          extension = argv + (strlen(argv) - 11);
          if ((strlen(argv) > 12) && (!strcmp(extension, ".WW3DShader")))
          {  // we should set this shader as the shader for the currently selected shape
             fprintf(stderr, "should be setting current shape's shader to this unarchived one...\n");
             return self;
          }
          
          // maybe it's a model template file...
          extension = argv + (strlen(argv) - 12);
          if ((strlen(argv) > 13) && (!strcmp(extension, ".mdlTemplate")))
          {  if (![self readMdlTemplateFile:argv])
             {  return nil;
             }
             return self;
          }
          
          // maybe it's a .nib file...
          extension = argv + (strlen(argv) - 4);
          if ((strlen(argv) > 5) && (!strcmp(extension, ".nib")))
          {  // really should have some way of dropping it so the camera's interp 
             // gets it, but I guess that's what a .cam.nib is...
             [[ribParser tclInterp] loadControlPanel:argv modally:NO];
	     return self;
	  }
       }
    }
    return nil;
}

- takeTreatAttributeBeginLikeStartShape:sender 
{  [ribParser takeTreatAttributeBeginLikeStartShape:sender];
   return self; 
}

- takeTreatTransformBeginLikeAttributeBegin:sender 
{  [ribParser takeTreatTransformBeginLikeAttributeBegin:sender];
   return self; 
}

- takeApplyShadersDirectlyToCurrentShape:sender 
{  [ribParser takeApplyShadersDirectlyToCurrentShape:sender];
   return self; 
}

- takeAmbientLightState:sender 
{  [camera takeAmbientLightState:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeAmbientLightIntensity:sender 
{  [camera takeAmbientLightIntensity:sender]; 
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel]; 
}
- takeAmbientLightColor:sender 
{  [camera takeAmbientLightColor:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeLeftLightState:sender 
{  [camera takeLeftLightState:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeLeftLightIntensity:sender 
{  [camera takeLeftLightIntensity:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeLeftLightColor:sender 
{  [camera takeLeftLightColor:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeRightLightState:sender 
{  [camera takeRightLightState:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeRightLightIntensity:sender 
{  [camera takeRightLightIntensity:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeRightLightColor:sender 
{  [camera takeRightLightColor:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}

- takeRenderWorldAsBox:sender 
{  [camera takeRenderWorldAsBox:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeRenderCurrentAsBox:sender 
{  [camera takeRenderCurrentAsBox:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeWorldIsVisible:sender 
{  [camera takeWorldIsVisible:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}
- takeCurrentIsVisible:sender 
{  [camera takeCurrentIsVisible:sender];  
   [delegate sceneWasUpdated:camera]; 
   return [self revertControlPanel];
}

- (BOOL)treatTransformBeginLikeAttributeBegin {  return [ribParser treatTransformBeginLikeAttributeBegin]; }

- (BOOL)renderWorldAsBox { return [camera renderWorldAsBox]; }
- (BOOL)renderCurrentAsBox { return [camera renderCurrentAsBox]; }
- (BOOL)worldIsVisible { return [camera worldIsVisible]; }
- (BOOL)currentIsVisible { return [camera currentIsVisible]; }

- (BOOL)ambientLightState { return [camera ambientLightState]; }
- (RtFloat)ambientLightIntensity { return [camera ambientLightIntensity]; }
- (NXColor)ambientLightColor { return [camera ambientLightColor]; }
- (BOOL)leftLightState { return [camera leftLightState]; }
- (RtFloat)leftLightIntensity { return [camera leftLightIntensity]; }
- (NXColor)leftLightColor { return [camera leftLightColor]; }
- (BOOL)rightLightState { return [camera rightLightState]; }
- (RtFloat)rightLightIntensity { return [camera rightLightIntensity]; }
- (NXColor)rightLightColor { return [camera rightLightColor]; }

- sendStringToCamera:sender 
{  
   int   ret;
   char  *retString;
   id    tclInterp = [camera tclInterp];
   BOOL  oldFlag = [tclInterp showErrors];


   if (cameraHistoryText)
   {  [cameraHistoryText setSel:[cameraHistoryText textLength] :[cameraHistoryText textLength]];
      [cameraHistoryText setSelGray:NX_BLACK];
      [cameraHistoryText replaceSel:"--> "];
      [cameraHistoryText setSelColor:sendTextColor];
      [cameraHistoryText replaceSel:[sender stringValue]];
      [cameraHistoryText replaceSel:"\n"];
      [cameraHistoryText scrollSelToVisible];
   }
   if ([sender respondsTo:@selector(selectedCell)])
   {  sender = [sender selectedCell];
   }

   // the the tcl interp we want to handle our own errors
   [tclInterp setShowErrors:NO];
   retString = [tclInterp eval:[sender stringValue] :&ret];
   [tclInterp setShowErrors:oldFlag];
   if (cameraHistoryText)
   {  if (ret)
      {  [cameraHistoryText setSelGray:NX_BLACK];
         [cameraHistoryText replaceSel:"ERROR: "];
         [cameraHistoryText replaceSel:retString];
         [cameraHistoryText replaceSel:"\n"];
         [cameraHistoryText scrollSelToVisible];
      }
      else
      {  [cameraHistoryText setSelGray:NX_BLACK];
         [cameraHistoryText replaceSel:"<-- "];
         [cameraHistoryText setSelColor:replyTextColor];
         [cameraHistoryText replaceSel:retString];
         [cameraHistoryText replaceSel:"\n"];
         [cameraHistoryText scrollSelToVisible];
      }
   }
   if (retString) { free(retString); }
   [cameraInterpTextField selectText:nil];

   return self;
}


- sendStringToShapes:sender 
{  
   int   ret;
   char  *retString;
   id    tclInterp = [ribParser tclInterp];
   BOOL  oldFlag = [tclInterp showErrors];


   if (shapeHistoryText)
   {  [shapeHistoryText setSel:[shapeHistoryText textLength] :[shapeHistoryText textLength]];
      [shapeHistoryText setSelGray:NX_BLACK];
      [shapeHistoryText replaceSel:"--> "];
      [shapeHistoryText setSelColor:sendTextColor];
      [shapeHistoryText replaceSel:[sender stringValue]];
      [shapeHistoryText replaceSel:"\n"];
      [shapeHistoryText scrollSelToVisible];
   }
   if ([sender respondsTo:@selector(selectedCell)])
   {  sender = [sender selectedCell];
   }

   // the the tcl interp we want to handle our own errors
   [tclInterp setShowErrors:NO];
   retString = [tclInterp eval:[sender stringValue] :&ret];
   [tclInterp setShowErrors:oldFlag];
   if (shapeHistoryText)
   {  if (ret)
      {  [shapeHistoryText setSelGray:NX_BLACK];
         [shapeHistoryText replaceSel:"ERROR: "];
         [shapeHistoryText replaceSel:retString];
         [shapeHistoryText replaceSel:"\n"];
         [shapeHistoryText scrollSelToVisible];
      }
      else
      {  [shapeHistoryText setSelGray:NX_BLACK];
         [shapeHistoryText replaceSel:"<-- "];
         [shapeHistoryText setSelColor:replyTextColor];
         [shapeHistoryText replaceSel:retString];
         [shapeHistoryText replaceSel:"\n"];
         [shapeHistoryText scrollSelToVisible];
      }
   }
   [shapeInterpTextField selectText:nil];

   return self;
}


- updateCameraInspector:sender { [self revertCameraInspector]; return self; }


// this is kind of a hack, but necessary...
- synchCameraToSceneClock
{
   [camera synchToSceneClock:sceneClock];

   return self;
}



/// new camera stuff
- takeFocalLength:sender  {  [camera takeFocalLength:sender]; return [self revertCameraInspector]; }
- takeFocalDistance:sender {  [camera takeFocalDistance:sender]; return [self revertCameraInspector]; }
- takeFStop:sender {  [camera takeFStop:sender]; return [self revertCameraInspector]; }
- takeExposureLength:sender {  [camera takeExposureLength:sender]; return [self revertCameraInspector]; }

- takeShotOutputTypeFromMatrix:sender {  [camera takeShotOutputTypeFromMatrix:sender]; return [self revertCameraInspector]; }

- takeShotLength:sender { [camera takeShotLength:sender]; return [self revertCameraInspector];}
- takeFramesPerSecond:sender; {  [camera takeFramesPerSecond:sender];  return [self revertCameraInspector]; }
- takeFrameNumber:sender {  [camera takeFrameNumber:sender];  return [self revertCameraInspector]; }

- takeExposureLengthFactor:sender { [camera takeExposureLengthFactor:sender]; return [self revertCameraInspector]; } 
- takeExposureLengthPercentage:sender { [camera setExposureLengthFactor:([sender floatValue] / 100.)]; return [self revertCameraInspector]; }
- takeShotStartTime:sender  {  [camera takeShotStartTime:sender]; return [self revertCameraInspector]; }
- takeExposureLengthAsStrobe:sender { [camera setExposureLengthFactor:0.0]; return [self revertCameraInspector]; }
- takeExposureLengthAsFilm:sender { [camera setExposureLengthFactor:0.5]; return [self revertCameraInspector]; }
- takeExposureLengthAsVideo:sender { [camera setExposureLengthFactor:1.0]; return [self revertCameraInspector]; }

// even newer camera stuff
- takeEyePoint:sender 
{ 
   RtPoint  anEyePoint, aViewPoint;
   float    aRollAngle;


   // need to make sure that the sender is a Matrix:
   if ([sender isKindOf:[Matrix class]])
   {  [camera getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
      N3D_XComp(anEyePoint) =  [[sender cellAt:0 :0] floatValue]; 
      N3D_YComp(anEyePoint) =  [[sender cellAt:0 :1] floatValue]; 
      N3D_ZComp(anEyePoint) =  [[sender cellAt:0 :2] floatValue]; 
      [camera setEyeAt:anEyePoint toward:aViewPoint roll:aRollAngle];
      [self revertCameraInspector]; 
      [camera display];
   }
   else
   {  NXLogError("should only send a takeEyePoint: msg from a subclass of Matrix (which %s isn't)", [[sender class] name]);
   }

   return self;
}


- takeLookAt:sender 
{ 
   RtPoint  anEyePoint, aViewPoint;
   float    aRollAngle;


   // need to make sure that the sender is a Matrix:
   if ([sender isKindOf:[Matrix class]])
   {  [camera getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
      N3D_XComp(aViewPoint) =  [[sender cellAt:0 :0] floatValue]; 
      N3D_YComp(aViewPoint) =  [[sender cellAt:0 :1] floatValue]; 
      N3D_ZComp(aViewPoint) =  [[sender cellAt:0 :2] floatValue]; 
      [camera setEyeAt:anEyePoint toward:aViewPoint roll:aRollAngle];
      [self revertCameraInspector]; 
      [camera display];
   }
   else
   {  NXLogError("should only send a takeLookAt: msg from a subclass of Matrix (which %s isn't)", [[sender class] name]);
   }

   return self;
}


- takeRollAngle:sender 
{ 
   RtPoint  anEyePoint, aViewPoint;
   float    aRollAngle;


   [camera getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
   aRollAngle = [sender floatValue]; 
   [camera setEyeAt:anEyePoint toward:aViewPoint roll:aRollAngle];
   [self revertCameraInspector]; 
   [camera display];

   return self;
}

- setCurrentShapeTransformMatrixToIdentity:sender
{
   [[camera currentShape] setTransformMatrix:(RtFloat (*)[4])N3DIdentityMatrix];
   [self revertControlPanel]; 
   [camera display];
   return self;
}

- takeCurrentShapeTransformMatrix:sender 
{  
   RtMatrix aMatrix;

 
   if ([sender isKindOf:[Matrix class]])
   {  aMatrix[0][0] =  [[sender cellAt:0 :0] floatValue]; 
      aMatrix[0][1] =  [[sender cellAt:0 :1] floatValue]; 
      aMatrix[0][2] =  [[sender cellAt:0 :2] floatValue]; 
      aMatrix[0][3] =  [[sender cellAt:0 :3] floatValue]; 

      aMatrix[1][0] =  [[sender cellAt:1 :0] floatValue]; 
      aMatrix[1][1] =  [[sender cellAt:1 :1] floatValue]; 
      aMatrix[1][2] =  [[sender cellAt:1 :2] floatValue]; 
      aMatrix[1][3] =  [[sender cellAt:1 :3] floatValue]; 

      aMatrix[2][0] =  [[sender cellAt:2 :0] floatValue]; 
      aMatrix[2][1] =  [[sender cellAt:2 :1] floatValue]; 
      aMatrix[2][2] =  [[sender cellAt:2 :2] floatValue]; 
      aMatrix[2][3] =  [[sender cellAt:2 :3] floatValue]; 

      aMatrix[3][0] =  [[sender cellAt:3 :0] floatValue]; 
      aMatrix[3][1] =  [[sender cellAt:3 :1] floatValue]; 
      aMatrix[3][2] =  [[sender cellAt:3 :2] floatValue]; 
      aMatrix[3][3] =  [[sender cellAt:3 :3] floatValue]; 
      
      [[camera currentShape] setTransformMatrix:aMatrix];
      [self revertControlPanel]; 
      [camera display];
   }
   else
   {  NXLogError("should only send a takeCurrentShapeTransformMatrix: msg from a subclass of Matrix (which %s isn't)", [[sender class] name]);
   }

   return self;
}

- setCameraPreTransformMatrixToIdentity:sender
{
   [camera setPreTransformMatrix:(RtFloat (*)[4])N3DIdentityMatrix];
   [self revertCameraInspector]; 
   [camera display];
   return self;
}

- takeCameraPreTransformMatrix:sender
{
   RtMatrix aMatrix;

 
   if ([sender isKindOf:[Matrix class]])
   {  aMatrix[0][0] =  [[sender cellAt:0 :0] floatValue]; 
      aMatrix[0][1] =  [[sender cellAt:0 :1] floatValue]; 
      aMatrix[0][2] =  [[sender cellAt:0 :2] floatValue]; 
      aMatrix[0][3] =  [[sender cellAt:0 :3] floatValue]; 

      aMatrix[1][0] =  [[sender cellAt:1 :0] floatValue]; 
      aMatrix[1][1] =  [[sender cellAt:1 :1] floatValue]; 
      aMatrix[1][2] =  [[sender cellAt:1 :2] floatValue]; 
      aMatrix[1][3] =  [[sender cellAt:1 :3] floatValue]; 

      aMatrix[2][0] =  [[sender cellAt:2 :0] floatValue]; 
      aMatrix[2][1] =  [[sender cellAt:2 :1] floatValue]; 
      aMatrix[2][2] =  [[sender cellAt:2 :2] floatValue]; 
      aMatrix[2][3] =  [[sender cellAt:2 :3] floatValue]; 

      aMatrix[3][0] =  [[sender cellAt:3 :0] floatValue]; 
      aMatrix[3][1] =  [[sender cellAt:3 :1] floatValue]; 
      aMatrix[3][2] =  [[sender cellAt:3 :2] floatValue]; 
      aMatrix[3][3] =  [[sender cellAt:3 :3] floatValue]; 
      
      [camera setPreTransformMatrix:aMatrix];
      [self revertCameraInspector]; 
      [camera display];
   }
   else
   {  NXLogError("should only send a takeCameraPreTransformMatrix: msg from a subclass of Matrix (which %s isn't)", [[sender class] name]);
   }

   return self;
}

// delegate methods

// this is sent whenever the contents of the well is changed
- wellWasUpdated:camera { [self display]; return self; }

// this method is sent whenever the scene changes in such a way that
// if the user rerendered the scene photorealistically, they would notice
// a difference.
- sceneWasUpdated:camera { return self; }

// this method is sent whenever the underlying controls on the scene were
// changed such that if the user manipulated the scene, it would react 
// differently
- cameraParametersWereUpdated:camera { [self invalidateCameraInspector]; return self; }
- controlsWereUpdated:camera { [self revertControlPanel]; return self; }


//---------------------------------------------------------------------------------------------------------
//	IB Methods
//---------------------------------------------------------------------------------------------------------

- (const char *)getInspectorClassName { return "WW3DWellIBInspector"; }


//---------------------------------------------------------------------------------------------------------
//	archiving Methods
//---------------------------------------------------------------------------------------------------------

#define typeVectorVersion1 "{ffff}{ffff}@if"
#define typeValuesVersion1 &bezelRect, &wellRect, &camera, &aspectRatioType, &aspectRatio

#define typeVectorVersion2 "{ffff}{ffff}@ifc"
#define typeValuesVersion2 &bezelRect, &wellRect, &camera, &aspectRatioType, &aspectRatio, &reuseImageView

#define typeVectorVersion3 "{ffff}{ffff}@ifc@"
#define typeValuesVersion3 &bezelRect, &wellRect, &camera, &aspectRatioType, &aspectRatio, &reuseImageView, &sceneClock

#define typeVector "@ifc@"
#define typeValues &camera, &aspectRatioType, &aspectRatio, &reuseImageView, &sceneClock

- read:(NXTypedStream*)stream 
{
    int version;
    
    [super read:stream];
    
    NX_DURING
	version = NXTypedStreamClassVersion(stream, "WW3DWell");
	if (version == 0) NXReadTypes(stream,"i",&version), version=1;
	if (version == 1) {
	    NXReadTypes(stream, typeVectorVersion1, typeValuesVersion1);
	    imageView = NXReadObject(stream);
	    reuseImageView = YES;
	    sceneClock = [[WWSceneClock alloc] init];
            useRendribInstead = NO;
            beepWhenDone = YES;
	}
	if (version == 2) {
	    NXReadTypes(stream, typeVectorVersion2, typeValuesVersion2);
	    imageView = NXReadObject(stream);
	    sceneClock = [[WWSceneClock alloc] init];
            useRendribInstead = NO;
            beepWhenDone = YES;
	}
	if (version == 3) {
	    NXReadTypes(stream, typeVectorVersion3, typeValuesVersion3);
	    imageView = NXReadObject(stream);
            useRendribInstead = NO;
            beepWhenDone = YES;
	}
	if (version == 4) {
	    NXReadTypes(stream, typeVector, typeValues);
	    NXReadRect(stream, &bezelRect);
	    NXReadRect(stream, &wellRect);
	    imageView = NXReadObject(stream);
            useRendribInstead = NO;
            beepWhenDone = YES;
	}
	if (version == 5) {
	    NXReadTypes(stream, typeVector, typeValues);
	    NXReadRect(stream, &bezelRect);
	    NXReadRect(stream, &wellRect);
	    imageView = NXReadObject(stream);
	    NXReadTypes(stream, "cc", &useRendribInstead, &beepWhenDone);
	}
    NX_HANDLER
	NXLogError("in read: %s, exception [%d] raised.\n", [[self class] name], NXLocalHandler.code);
	return nil;
    NX_ENDHANDLER
    
    return self;
}

- write:(NXTypedStream*)stream 
{
    [super write:stream];
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteRect(stream, &bezelRect);
    NXWriteRect(stream, &wellRect);
    NXWriteObjectReference(stream, imageView);
    NXWriteTypes(stream, "cc", &useRendribInstead, &beepWhenDone);
    return self;
}


@end
